/* Look Awesome — Design System.
   Chrome theming is driven entirely by WA design tokens. The Look
   Awesome look in the database (LookTheme + ThemeHelper +
   <style id="look-awesome-theme"> in the layout) supplies the
   --wa-color-*-XX ladder at :root, plus per-mode + per-scope re-
   emissions for body:has(.look-layout) on /show and /edit. WA's
   own theme then derives surface/text/border/etc. from that ladder.
   No custom --ba-* tokens — we lean on WA. */

:root {
  /* WA token overrides */
  --wa-color-neutral-border-quiet: var(--wa-color-surface-border);
  --wa-color-neutral-border-normal: var(--wa-color-surface-border);
  --wa-color-neutral-border-loud: var(--wa-color-surface-border);
  --wa-color-neutral-on-quiet: var(--wa-color-text-quiet);
  --wa-color-neutral-on-normal: var(--wa-color-text-quiet);
  --wa-form-control-label-color: var(--wa-color-text-normal);
  --wa-form-control-label-font-weight: 500;
  --wa-form-control-label-font-size: 14px;
  /* Brand outlined button overrides — softer contrast */
  --wa-color-brand-border-loud: var(--wa-color-surface-border);
  --wa-color-brand-on-quiet: var(--wa-color-brand-60);

  /* Shadow scale extension. WA ships --wa-shadow-{s,m,l} where each
     step doubles the previous base unit (0.125rem → 0.25rem → 0.5rem).
     Continuing the same pattern for the hover-lift state on clickable
     cards: xl uses 1rem, single-layer, 12% alpha, color-mix in oklab
     (matches WA's exact convention so it dark-mode-adapts identically). */
  --wa-shadow-xl: 0 1rem 1rem -0.5rem color-mix(in oklab, #121212 12%, transparent);

  /* Brand color scale — generated from #EB6FBD via OKLCH contrast targeting */
  --wa-color-brand-95: #ffedf7;
  --wa-color-brand-90: #ffdcef;
  --wa-color-brand-80: #ffb2df;
  --wa-color-brand-70: #ff85d0;
  --wa-color-brand-60: #e469b7;
  --wa-color-brand-50: #c04796;
  --wa-color-brand-40: #9e2678;
  --wa-color-brand-30: #860263;
  --wa-color-brand-20: #65004a;
  --wa-color-brand-10: #40002d;
  --wa-color-brand-05: #2b001e;
  --wa-color-brand: #eb6fbd;
  --wa-color-brand-on: white;
}

.wa-dark {
  --wa-color-neutral-border-quiet: var(--wa-color-surface-border);
  --wa-color-neutral-border-normal: var(--wa-color-surface-border);
  --wa-color-neutral-border-loud: var(--wa-color-surface-border);
  --wa-color-neutral-on-quiet: var(--wa-color-text-quiet);
  --wa-color-neutral-on-normal: var(--wa-color-text-quiet);
  /* Brand outlined button overrides — softer contrast */
  --wa-color-brand-border-loud: var(--wa-color-surface-border);
  --wa-color-brand-on-quiet: var(--wa-color-brand-50);

  /* Brand color scale — same tints, referenced via accent for dark mode */
  --wa-color-brand-95: #ffedf7;
  --wa-color-brand-90: #ffdcef;
  --wa-color-brand-80: #ffb2df;
  --wa-color-brand-70: #ff85d0;
  --wa-color-brand-60: #e469b7;
  --wa-color-brand-50: #c04796;
  --wa-color-brand-40: #9e2678;
  --wa-color-brand-30: #860263;
  --wa-color-brand-20: #65004a;
  --wa-color-brand-10: #40002d;
  --wa-color-brand-05: #2b001e;
  --wa-color-brand: #eb6fbd;
  --wa-color-brand-on: white;
}

/* Color rows stack vertically inside the narrow center column. */
.saved-colors-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--wa-space-m);
}

.color-delete-btn {
  position: absolute;
  /* 44x44 hit area with a 32px × icon visually at the card corner.
     The duotone fa-circle-xmark renders as a filled circle with the
     × cut out via primary color = background, so the icon itself
     reads as a button shape. */
  top: -12px;
  right: -12px;
  width: 44px;
  height: 44px;
  z-index: 1;
  font-size: 32px;
  line-height: 1;
  color: var(--wa-color-text-quiet);
  text-decoration: none;
  opacity: 0;
  transition: opacity 150ms ease-out;
  --fa-primary-color: var(--wa-color-surface-default);
  --fa-secondary-color: var(--wa-color-text-quiet);
  --fa-secondary-opacity: 1;
  /* Full <button> reset so the element looks identical regardless of
     whether it renders as <a> or <button>. Browsers inject their own
     font, appearance, and baseline alignment on buttons. */
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  /* Inherit family/weight only. `font: inherit` shorthand would silently
     reset font-size and override the explicit size set above. */
  font-family: inherit;
  font-weight: inherit;
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.color-delete-btn:hover {
  --fa-secondary-color: var(--wa-color-text-normal);
}
wa-card:hover .color-delete-btn {
  opacity: 1;
}

/* Social actions on gallery cards (copy + favorite, plus share URL on
   large preview cards). Sits at the bottom-right of the card, OUTSIDE
   the navigating .card-link wrapper (forms can't nest inside <a>).
   z-index lifts the cluster above the link's click overlay. Vertically
   centered on .card-name via transform: translateY(50%) over the
   .card-name center line — height-agnostic to whatever the action
   button's computed height ends up being. Heart glyph's right edge
   aligns with the rightmost color block via margin-right: -8px
   (overcomes card-name's 16px right padding minus the swatch-edge
   inset minus the FA SVG fixed-width gutter; see _look_card.html.erb
   for context). */
.card-actions {
  position: absolute;
  /* Locked from wa-card top: card-colors is a fixed 182px tall
     (no body padding on regular cards, 12px pad-top + 170px row
     content), and we want the cluster centered on the original
     card-name's title row. So top = 182 + half the regular
     card-name height; transform: translateY(-50%) does the
     center-on-cluster math. Anchored from the top means the icons
     stay put when card-name grows downward. Large preview cards
     have a different geometry (16px body padding, no card-colors
     inner pad, card-name-large min-height 60px) — see the
     .look-card-large .card-actions override below. */
  top: calc(182px + (var(--wa-font-size-l) * 1.15 * 2 + var(--wa-space-s) * 2) / 2);
  right: var(--wa-space-m);
  margin-right: -8px;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  gap: 0;
}
/* Large preview cards (/show): 16px body padding lifts everything
   16px, the chroma's gallery-default 12px inner pad is zeroed, and
   card-name-large min-height drops to 60px with 16px top padding
   (no bottom). The title centerline lands at 223px from the wa-card
   top — translateY(-50%) does the rest. */
.look-card-large .card-actions {
  top: 223px;
}
.card-action {
  background: transparent;
  border: none;
  padding: var(--wa-space-3xs);
  margin: 0;
  cursor: pointer;
  font: inherit;
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  color: var(--wa-color-text-quiet);
  font-size: var(--wa-font-size-l);
  line-height: 1;
  border-radius: var(--wa-border-radius-s);
  transition: color 150ms ease-out;
}
.card-action-favorite:hover,
.card-action-favorite.is-favorited,
.card-action-copy:hover,
.card-action-share:hover {
  color: var(--wa-color-brand-fill-loud);
}

/* Favorite turbo-frame must reserve its visible button's space so the
 * mid-swap empty state doesn't collapse the layout. Without this, when
 * the frame's content is briefly removed during the response swap, the
 * cluster's right-anchor causes the copy to shift right under the
 * cursor — which then highlights as if hovered. See DESIGN.md "No
 * Layout Jank → transient mid-swap shifts."
 *
 * min-width/height match the heart button's rendered box (29px) so a
 * mid-swap empty frame doesn't shrink and let the title absorb the
 * freed space. */
.favorite-frame {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  min-width: 30px;
  min-height: 30px;
}

/* Eyedropper button on each color card when a source image is present.
   Visual mirrors the × delete (fa-duotone fa-circle-xmark): an FA stack
   with a solid circle (secondary color) and the eyedropper glyph on top
   (bg color, "cut out" effect). 44x44 hit target, 32px visible circle. */
.color-eyedropper-btn {
  position: absolute;
  top: -12px;
  left: -12px;
  z-index: 1;
  width: 44px;
  height: 44px;
  /* fa-stack base unit. fa-stack-2x circle renders at 32px, matching the
     delete icon's 32px font-size. fa-stack-1x eyedropper renders at 16px
     centered inside the circle. */
  font-size: 16px;
  line-height: 1;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-family: inherit;
  font-weight: inherit;
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 150ms ease-out;
}
/* Layer colors: matches the delete icon's primary/secondary scheme so
   both corner buttons read as the same visual treatment. */
.color-eyedropper-btn .fa-circle { color: var(--wa-color-text-quiet); transition: color 150ms ease-out; }
.color-eyedropper-btn .fa-eye-dropper { color: var(--wa-color-surface-default); }

wa-card:hover .color-eyedropper-btn {
  opacity: 1;
}
.color-eyedropper-btn:hover .fa-circle,
.color-eyedropper-btn:focus-visible .fa-circle {
  color: var(--wa-color-text-normal);
}

/* Pulsing outline on the card whose color will be replaced by the next
   image click. Reminds the user what they're about to swap. */
wa-card.color-card-picking {
  outline: 2px solid var(--wa-color-brand-60, #ee5c6c);
  outline-offset: 2px;
  animation: color-card-pick-pulse 1.2s ease-in-out infinite;
}
wa-card.color-card-picking .color-eyedropper-btn {
  opacity: 1;
}
wa-card.color-card-picking .color-eyedropper-btn .fa-circle {
  color: var(--wa-color-brand-60, #ee5c6c);
}
@keyframes color-card-pick-pulse {
  0%, 100% { outline-color: var(--wa-color-brand-60, #ee5c6c); }
  50% { outline-color: var(--wa-color-brand-30, #ff9aab); }
}

/* Match wa-file-input's dropzone to the ghost-tile pattern used
   elsewhere in the app (.source-image-ghost, color ghost card).
   Tokens reference the WA neutral + brand ladders directly so the
   ghost picks up the active look inside .look-layout and CA
   chrome elsewhere — var() resolution is lazy at the element so
   each match re-evaluates against the closest scoped ladder. */
wa-file-input::part(dropzone) {
  border: 1.5px dashed var(--wa-color-neutral-90);
  border-radius: 8px;
  background: white;
  color: var(--wa-color-neutral-40);
  transition: border-color 120ms ease-out, color 120ms ease-out, background 120ms ease-out;
}
.wa-dark wa-file-input::part(dropzone) {
  border-color: var(--wa-color-neutral-20);
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
  color: var(--wa-color-neutral-60);
}
wa-file-input::part(dropzone):hover,
wa-file-input:state(dragging)::part(dropzone) {
  border-color: var(--wa-color-brand-fill-loud);
  color: var(--wa-color-neutral-10);
  background: var(--wa-color-neutral-95);
}
.wa-dark wa-file-input::part(dropzone):hover,
.wa-dark wa-file-input:state(dragging)::part(dropzone) {
  color: var(--wa-color-neutral-95);
  background: var(--wa-color-neutral-05);
}
/* Inside-the-slot caption — "JPG, PNG, GIF, WebP · up to 10 MB". */
.dropzone-hint { font-size: 12px; margin-top: 4px; }

/* ─── Source images row (edit page) ─── */
/* Horizontal row of up-to-4 image thumbnails + a dashed ghost tile at
   the right. Flex-wrap drops extra tiles to a new line when the row +
   look name exceed the available width. */
.source-images-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-start;
}
.source-image-tile {
  position: relative;
  flex: 0 0 auto;
}
/* Shrink the inherited .color-delete-btn to fit a 56px thumbnail — the
   default 44×44 hit area + 32px icon is sized for the larger color
   cards. Icon sits on the tile's top-right corner with a -8/-8 overhang,
   matching the card-delete visual pattern but scaled down. */
.source-image-delete {
  top: -8px !important;
  right: -8px !important;
  width: 28px !important;
  height: 28px !important;
  /* 24px matches the visible glyph size of the .source-image-copy
     fa-stack (stack-2x circle = font-size × 2 = 12px × 2 = 24px),
     so the two corner buttons read as the same visual weight. */
  font-size: 24px !important;
}
.source-image-tile:hover .source-image-delete,
.source-image-delete:focus-visible {
  opacity: 1;
}

/* Ghost tile — dashed border, matches the color-card ghost pattern.
   Hidden when the 4-image cap is hit (server-side) and not rendered. */
.source-image-ghost {
  width: 56px;
  height: 56px;
  border: 1.5px dashed var(--wa-color-neutral-90);
  border-radius: 8px;
  background: white;
  color: var(--wa-color-neutral-40);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: border-color 120ms ease-out, color 120ms ease-out, background 120ms ease-out;
  font-size: 22px;
  padding: 0;
}
.wa-dark .source-image-ghost {
  border-color: var(--wa-color-neutral-20);
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
  color: var(--wa-color-neutral-60);
}
.source-image-ghost:hover,
.source-image-ghost:focus-visible {
  border-color: var(--wa-color-brand-fill-loud);
  color: var(--wa-color-neutral-10);
  background: var(--wa-color-neutral-95);
  outline: none;
}
.wa-dark .source-image-ghost:hover,
.wa-dark .source-image-ghost:focus-visible {
  color: var(--wa-color-neutral-95);
  background: var(--wa-color-neutral-05);
}
/* Upload modal — URL row + dropzone, mirroring the create-page patterns. */
.source-image-upload-dialog::part(body) { padding: 24px; }
.source-image-upload-dialog { --width: 440px; }
.source-image-url-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
  margin-bottom: 16px;
}
.source-image-url-row wa-input { flex: 1 1 auto; min-width: 0; }
.source-image-upload-or {
  text-align: center;
  color: var(--wa-color-text-quiet);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 8px 0;
}
.source-image-upload-note {
  color: var(--wa-color-text-quiet);
  text-align: center;
  margin: var(--wa-space-m) 0 0 0;
}

/* ─── Image sampler modal ─── */
/* Small thumbnail on the edit page that opens the sampling modal. */
.source-image-thumb {
  width: 56px;
  height: 56px;
  border: 1px solid var(--wa-color-surface-border);
  border-radius: 8px;
  padding: 0;
  overflow: hidden;
  cursor: zoom-in;
  background: var(--wa-color-surface-raised);
  transition: border-color 150ms ease-out, transform 150ms ease-out;
}

/* Copy-source-URL button overlaid on the thumbnail's bottom-right
   corner. Same fa-stack circle + glyph treatment as the eyedropper on
   color cards — the circle is secondary-colored, the link glyph uses
   bg color for a "cut out" look. Overhangs the thumbnail by 8px to
   match the card-delete-btn / eyedropper visual pattern. */
.source-image-wrap {
  position: relative;
  flex-shrink: 0;
}
.source-image-copy {
  position: absolute;
  bottom: -8px;
  right: -8px;
  width: 28px;
  height: 28px;
  /* fa-stack base — 2x circle = 24px, 1x glyph = 12px. */
  font-size: 12px;
  line-height: 1;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.source-image-copy .fa-circle {
  color: var(--wa-color-text-quiet);
  transition: color 150ms ease-out;
}
.source-image-copy .fa-link,
.source-image-copy .fa-check {
  color: var(--wa-color-surface-default);
}
.source-image-copy:hover .fa-circle,
.source-image-copy:focus-visible .fa-circle {
  color: var(--wa-color-text-normal);
}
.source-image-thumb:hover {
  border-color: var(--wa-color-text-quiet);
  transform: scale(1.02);
}
.source-image-thumb:focus-visible {
  outline: 2px solid var(--wa-color-brand-fill-loud);
  outline-offset: 2px;
}
.source-image-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Export modal — wa-dialog with format tabs + a single copy button
   in the footer. Uses the bare-dialog + .dialog-close pattern from
   .image-sample-dialog (see "Shared bare dialog treatment" below).
   Pre-formatted code blocks scroll vertically when long;
   horizontal scroll handled inline by .code-block. */
/* Pin width AND height so switching tabs never resizes the dialog. */
.export-dialog::part(dialog) {
  width: min(720px, 95vw);
  height: min(720px, 90vh);
}
/* Flex chain from the dialog body all the way down to .code-block so
   the <pre> fills whatever vertical space is left after the tab strip,
   description, and footer. Description takes its natural line count;
   the pre stretches to absorb the difference. min-height: 0 at each
   step lets the pre shrink below its content height so its own
   overflow: auto kicks in on long formats.
   WA's wa-tab-panel shadow CSS hardcodes `padding: 32px 0` on its
   ::part(base) slot — the override here also installs the flex
   container at that level. */
.export-dialog::part(body) {
  display: flex;
  flex-direction: column;
  /* WA's [part="dialog"] is already display:flex column, but the body's
     default min-height:auto lets it grow to content. Force shrink. */
  min-height: 0;
  overflow: hidden;
}
/* wa-tab-group's shadow wraps everything (nav + body) in a single
   `[part="base"]` div with `flex: 0 1 auto` — that keeps the wrapper
   at content size and breaks the flex chain. Override the base to
   fill the host so its child body (flex:1 1 0%) can in turn fill,
   and so on down to the .code-block.
   The wa-tab-panel selector only matches the [active] panel so we
   don't override WA's `display: none` on inactive panels (otherwise
   all five panels share the body's space as flex siblings). */
.export-dialog wa-tab-group,
.export-dialog wa-tab-group::part(base),
.export-dialog wa-tab-group::part(body),
.export-dialog wa-tab-panel[active] {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.export-dialog wa-tab-panel::part(base) {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  padding: var(--wa-space-m) 0;
}
.export-dialog .code-block {
  flex: 1;
  min-height: 0;
  margin: 0;
  overflow: auto;
}
/* Description above each export panel — tells the user what the format
   is and where to put it before they hit Copy. Inline <code> samples
   render as compact pills so the prose stays readable. The bottom
   margin matches the panel's own --wa-space-m padding so above /
   between / below all share the same rhythm. */
.export-format-description {
  font-size: var(--wa-font-size-s);
  color: var(--wa-color-text-quiet);
  line-height: 1.45;
  margin: 0 0 var(--wa-space-m) 0;
}
.export-format-description code {
  font-family: var(--wa-font-family-code);
  font-size: 0.92em;
  background: var(--wa-color-neutral-fill-quiet);
  color: var(--wa-color-text-normal);
  padding: 0.1em 0.35em;
  border-radius: 3px;
}
.export-dialog-footer {
  display: flex;
  justify-content: flex-end;
  width: 100%;
}

/* wa-dialog sizing. Panel spans near the full viewport so the image has
   room to breathe; body padding is kept tight since we drop the header
   and footer to maximize image real-estate. */
/* Dialog sizes to content (image + swatches + padding) instead of a
   fixed 90vw × 90vh box. Max caps keep it within the viewport on any
   display. The image has its own max-width/height that account for
   swatches + padding, so the content never exceeds the max. */
/* Universal: take ALL <wa-dialog> hosts out of normal flow.
   When a wa-dialog toggles from display:none → display:block, the host
   element returns to flex/grid flow and ADDS layout gap (24px wa-gap-l
   inside .edit-main, etc.) — that visibly pushes sibling content down
   while the modal is open. The actual visible dialog lives in the
   browser's TOP LAYER (native <dialog> element via showModal), so the
   host's flow position is irrelevant for rendering.
   Applying this globally so every new wa-dialog inherits the fix
   automatically — easy to forget per-dialog (we have. twice.). */
wa-dialog {
  position: absolute;
}

.image-sample-dialog {
  --width: auto;
}
/* Web Awesome wa-dialog exposes these parts: dialog, header, title,
   header-actions, close-button, body. No `panel` part — size the
   native <dialog> element directly. */
.image-sample-dialog::part(dialog) {
  /* fit-content + max caps wraps the dialog to its content. WA's
     internal `display: flex` + `width: var(--width)` resolves to a
     fixed pixel size, so explicit fit-content is the only way to
     get content-driven sizing. */
  width: fit-content;
  height: fit-content;
  max-width: 90vw;
  max-height: 90vh;
  /* Kill the `transition: all` that wa-dialog ships on this part —
     turbo-frame refresh replaces the dialog on every image-sample
     commit, and the new dialog's default transition plays out as a
     visible bounce (4% scale-up from initial to final size) every
     time. Snap-open reads as "fast" rather than janky. */
  transition: none !important;
}
/* Shared "bare dialog" treatment — applies to any dialog that uses
   the custom .dialog-close button instead of WA's built-in header
   close. Hides the built-in header, relaxes overflow on every
   layer so the -12/-12 close button doesn't get clipped, and gives
   the body 16px padding + relative positioning as the close
   button's offset anchor.

   To add a new bare dialog: append its class to all FOUR selector
   lists in this section, matching the pattern. See CLAUDE.md →
   "Dialog pattern" for the full template (markup + CSS). */
.image-sample-dialog,
.image-sample-dialog::part(dialog),
.image-sample-dialog::part(body),
.export-dialog,
.export-dialog::part(dialog),
.export-dialog::part(body),
.delete-look-dialog,
.delete-look-dialog::part(dialog),
.delete-look-dialog::part(body),
.delete-account-dialog,
.delete-account-dialog::part(dialog),
.delete-account-dialog::part(body),
.discard-changes-dialog,
.discard-changes-dialog::part(dialog),
.discard-changes-dialog::part(body),
.discard-draft-dialog,
.discard-draft-dialog::part(dialog),
.discard-draft-dialog::part(body),
.color-detail-dialog,
.color-detail-dialog::part(dialog),
.color-detail-dialog::part(body) {
  overflow: visible;
}
.image-sample-dialog::part(body),
.export-dialog::part(body),
.delete-look-dialog::part(body),
.delete-account-dialog::part(body),
.discard-changes-dialog::part(body),
.discard-draft-dialog::part(body),
.color-detail-dialog::part(body) {
  padding: 16px;
  position: relative;
}
/* Zero out the export-dialog's body bottom padding so the slot's
   --wa-space-l padding-bottom (set on wa-tab-panel::part(base) above)
   becomes the only gap between the code block and the footer — equal
   rhythm with the gaps around the description. Override lives here so
   it cascades over the shared bare-dialog rule above. */
.export-dialog::part(body) {
  padding-bottom: 0;
}
.image-sample-dialog::part(header),
.image-sample-dialog::part(close-button),
.export-dialog::part(header),
.export-dialog::part(close-button),
.delete-look-dialog::part(header),
.delete-look-dialog::part(close-button),
.delete-account-dialog::part(header),
.delete-account-dialog::part(close-button),
.discard-changes-dialog::part(header),
.discard-changes-dialog::part(close-button),
.discard-draft-dialog::part(header),
.discard-draft-dialog::part(close-button),
.color-detail-dialog::part(header),
.color-detail-dialog::part(close-button) {
  display: none;
}

/* Custom dialog close. Same fa-duotone circle-xmark as the card deletes,
   but positioned inside the panel's top-right corner (native <dialog>
   clips top-layer children, so the overhang pattern used on wa-cards
   isn't an option here). Negative offsets pull against the body's 16px
   padding so the icon reads as being AT the corner — matching the
   visual balance of the on-card delete buttons as closely as possible
   without overflowing. Always visible. */
.dialog-close {
  /* Let .color-delete-btn's base top: -12px / right: -12px apply so the
     X sits on the panel's outer corner exactly like the card deletes.
     The overflow: visible cascade above on every dialog part (including
     ::part(dialog), the native <dialog>) prevents clipping. */
  /* Drop the 44×44 hit-area padding from .color-delete-btn — the icon
     itself is the button here. Mouse-only dialog, no touch precision
     concern. */
  width: 32px;
  height: 32px;
  opacity: 1;
  z-index: 10;
  /* Flip the duotone from the .color-delete-btn defaults. Circle (ring)
     fills with --wa-color-surface-raised (the token WA's dialog panel
     uses for its own bg) so the button's disc matches the modal panel
     seamlessly in BOTH light AND dark mode. X (primary) is the muted
     text-quiet so it reads without shouting. */
  --fa-primary-color: var(--wa-color-text-quiet);
  --fa-secondary-color: var(--wa-color-surface-raised);
  --fa-secondary-opacity: 1;
}
.dialog-close:hover {
  /* Override .color-delete-btn:hover. Hover flips to red ring + light X
     so the whole button lights up as the destructive cue, not just the
     internal glyph. Light mode: white X on red. Dark mode: black X on
     red (overridden below) — darker text reads better over the
     saturated red when the surrounding UI is already dark. */
  --fa-primary-color: white;
  --fa-secondary-color: var(--wa-color-danger-50);
}
.wa-dark .dialog-close:hover {
  --fa-primary-color: black;
}

/* Color-detail prev/next nav. Sits OUTSIDE the modal panel — over
   the backdrop — with flipped fa-duotone colors so the chevron disc
   reads as part of the dimmed background, not the panel. Chevron
   glyph picks up surface-raised (the panel color) so it reads as
   "punched through" the disc; the disc itself uses text-quiet for
   low backdrop contrast. The overflow: visible cascade on
   ::part(body) keeps the chevrons rendered when they extend past
   the body edge. */
.color-detail-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  z-index: 10;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-size: 32px;
  line-height: 1;
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  --fa-primary-color: var(--wa-color-surface-raised);
  --fa-secondary-color: var(--wa-color-text-quiet);
  --fa-secondary-opacity: 1;
  transition: transform 120ms ease-out;
}
.color-detail-nav.prev { left: -44px; }
.color-detail-nav.next { right: -44px; }
.color-detail-nav:hover {
  --fa-secondary-color: var(--wa-color-text-normal);
}
.color-detail-nav.prev:hover { transform: translateY(-50%) translateX(-2px); }
.color-detail-nav.next:hover { transform: translateY(-50%) translateX(2px); }

/* Image + swatches layout, flipped by orientation class. Landscape
   (is-wide): stack vertically with swatches below. Portrait (is-tall):
   row with swatches to the right. */
.image-sample-layout {
  /* Normal flow — body sizes to layout, dialog sizes to body. Each
     orientation puts the swatches on the appropriate axis. */
  display: flex;
  gap: 16px;
}
.image-sample-layout.is-wide {
  flex-direction: column;
  align-items: center;
}
.image-sample-layout.is-tall {
  /* row-reverse puts the swatches column on the LEFT while keeping
     image-first DOM order. Prevents the swatches' top-right delete
     buttons from visually colliding with the modal's top-right close. */
  flex-direction: row-reverse;
  align-items: stretch;
}

.image-sample-image-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
}
/* Image caps reserve room for swatches + body padding so the modal
   wraps content without overflowing 90vw/90vh. Buffer is orientation-
   specific: landscape reserves it on height (swatches sit below);
   portrait reserves it on width (swatches sit to the right). */
.image-sample-image-wrap img {
  border-radius: 8px;
  object-fit: contain;
  cursor: crosshair;
  user-select: none;
}
/* Image sizing uses min() with the image's aspect ratio (injected as
   --aspect via inline style from ActiveStorage metadata) to pick
   whichever axis is actually binding. Without this, a landscape image
   forced to 90vw would letterbox inside a too-wide box; a portrait
   image forced to 90vh would letterbox vertically. The dialog is
   fit-content, so the image's definite rendered dimensions also size
   the dialog wrapper with no extra whitespace. --aspect falls back to
   1 if blob metadata isn't available. */
.image-sample-layout.is-wide .image-sample-image-wrap img {
  /* Landscape: width capped at 90vw - 32 OR (90vh - 132) scaled by
     aspect, whichever is smaller. Height derives via aspect-ratio. */
  width: min(calc(90vw - 32px), calc((90vh - 132px) * var(--aspect, 1)));
  height: auto;
  max-width: calc(90vw - 32px);
  max-height: calc(90vh - 132px);
}
.image-sample-layout.is-tall .image-sample-image-wrap img {
  /* Portrait: height capped at 90vh - 32 OR (90vw - 132) / aspect,
     whichever is smaller. Width derives via aspect-ratio. */
  height: min(calc(90vh - 32px), calc((90vw - 132px) / var(--aspect, 1)));
  width: auto;
  max-width: calc(90vw - 132px);
  max-height: calc(90vh - 32px);
}

/* Swatch strip. 6px padding + -6px margin cancels out visually (swatches
   sit in the same place as with no padding, so the 16px body-padding +
   16px layout-gap rhythm is preserved), but the padding gives the
   delete-button overhang and hover ring 6px of visible-overflow slack
   INSIDE the container — necessary when overflow-y: auto kicks in at
   short viewports. */
.image-sample-swatches {
  display: flex;
  gap: 10px;
  align-content: flex-start;
  flex-shrink: 0;
  padding: 6px;
  margin: -6px;
}
.image-sample-layout.is-wide .image-sample-swatches {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
}
.image-sample-layout.is-tall .image-sample-swatches {
  flex-direction: column;
  flex-wrap: nowrap;
  /* Cap to the dialog's interior height (90vh - body padding) and
     allow scrolling when a look has more swatches than the viewport
     can fit in a single column. */
  max-height: calc(90vh - 32px);
  overflow-y: auto;
}

/* Square swatch preview inside the modal. Mirror the delete-on-hover
   affordance from the saved-color cards so both surfaces read the same. */
.modal-swatch {
  position: relative;
  width: 56px;
  height: 56px;
  border-radius: 6px;
  border: 1px solid var(--wa-color-surface-border);
  cursor: pointer;
  flex-shrink: 0;
}
/* Hover and pick-target rings use inset box-shadow (not outline or
   outward shadow) so they stay visible inside the scrollable swatch
   column — the overflow: auto clips anything reaching past the
   swatch's own edge. */
.modal-swatch:hover {
  box-shadow: inset 0 0 0 2px var(--wa-color-text-quiet);
}
.modal-swatch:focus-visible {
  box-shadow: inset 0 0 0 2px var(--wa-color-brand-fill-loud);
}
.modal-swatch.is-pick-target {
  box-shadow: inset 0 0 0 3px var(--wa-color-brand-60, #ee5c6c);
  animation: modal-swatch-pick-pulse 1.2s ease-in-out infinite;
}
@keyframes modal-swatch-pick-pulse {
  0%, 100% { box-shadow: inset 0 0 0 3px var(--wa-color-brand-60, #ee5c6c); }
  50%      { box-shadow: inset 0 0 0 3px var(--wa-color-brand-30, #ff9aab); }
}

/* Delete × offset from the swatch's top-right corner. -6px matches the
   6px of slack the swatches-container padding reserves, so the overhang
   lands inside the container's padding box and doesn't get clipped by
   overflow-y: auto. Same 32px icon size as the modal-close and
   card-delete buttons elsewhere. */

/* Ghost add-card — always the last item in .saved-colors-grid. Dashed
   border + muted fill signals "not a saved color yet, fill me in to
   add one". When the look has zero colors, this is the only card
   in the grid and reads as the empty state. Clicks on the + fire
   `image-sample#ghostAddColor` which POSTs add_color and partial-
   refreshes the grid + swatches + preview. */
.ghost-add-card {
  position: relative;
  padding: 16px;
  border: 2px dashed var(--wa-color-neutral-90);
  border-radius: 12px;
  /* Match the saved-color wa-cards' filled background so the ghost
     reads as a "same-shape slot" rather than a see-through hole in
     the grid. Dashed border (vs solid) is the signal it's not a
     committed color yet. */
  background: white;
  transition: border-color 150ms ease-out;
}
.wa-dark .ghost-add-card {
  border-color: var(--wa-color-neutral-20);
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
}
.ghost-add-card:hover,
.ghost-add-card:focus-within {
  /* Mid-tone between neutral-90 and neutral-40 in the active look
     — darker than default so the hover reads, lighter than the muted
     text-quiet shade so it stays subtle. */
  border-color: color-mix(in srgb, var(--wa-color-neutral-40) 35%, var(--wa-color-neutral-90));
}
.wa-dark .ghost-add-card:hover,
.wa-dark .ghost-add-card:focus-within {
  border-color: color-mix(in srgb, var(--wa-color-neutral-60) 35%, var(--wa-color-neutral-20));
}
/* Reuse .color-delete-btn sizing/positioning but swap the glyph to
   circle-plus. Secondary color is the brand fill so the "+" reads
   as the primary CTA for adding a color. Always visible (not
   opacity-hover). Disabled state when no color has been entered yet —
   button reads as neutral-colored and ignores clicks. */
.ghost-add-card-plus {
  opacity: 1;
  --fa-primary-color: var(--wa-color-neutral-95);
  --fa-secondary-color: var(--wa-color-brand-fill-loud);
  transition: opacity 150ms ease-out;
}
.wa-dark .ghost-add-card-plus {
  --fa-primary-color: var(--wa-color-neutral-05);
}
.ghost-add-card-plus:hover {
  --fa-secondary-color: var(--wa-color-brand-50);
}
.ghost-add-card-plus.disabled {
  /* Soft hint, not "disabled" — neutral-fill-normal (#e5e5e5 light /
     dark-mode equivalent) is one tier below text-quiet so the icon
     reads as a quiet placeholder, not a button you can't press.
     Primary still hits surface-default so the cross "carves" into
     the ring. */
  --fa-primary-color: var(--wa-color-surface-default);
  --fa-secondary-color: var(--wa-color-neutral-fill-normal);
  pointer-events: none;
  cursor: not-allowed;
}

/* Empty state placeholder for the modal swatches column — dashed
   outline in the shape of a swatch, shown when the look has zero
   colors so the column still takes up its expected visual slot. */
.modal-swatch-placeholder {
  width: 56px;
  height: 56px;
  border: 2px dashed var(--wa-color-surface-border);
  border-radius: 6px;
  flex-shrink: 0;
}

.modal-swatch .color-delete-btn {
  top: -6px;
  right: -6px;
  width: 24px;
  height: 24px;
  font-size: 24px;
}
.modal-swatch:hover .color-delete-btn,
.modal-swatch:focus-visible .color-delete-btn {
  opacity: 1;
}


wa-breadcrumb {
  --wa-color-text-link: var(--wa-color-text-quiet);
}
wa-breadcrumb wa-breadcrumb-item::part(label):hover {
  color: var(--wa-color-brand-fill-loud);
}
wa-breadcrumb wa-breadcrumb-item:last-child {
  color: var(--wa-color-text-quiet);
}

/* Filled cards use a synthetic "step ~97" tint — look neutral-95
   mixed 50% with white in OKLAB space — so they read one perceptual
   step lighter than the body bg (which uses neutral-95 directly). The
   active look's neutral hue character carries through the mix, so
   on Vernazza the cards lift as a slightly icier near-white above the
   icy bg; on a warm-neutral look they lift as a slightly warmer
   near-white above the warm bg. Dark mode mixes neutral-05 with a
   smaller white percentage (12%) so cards stay clearly dark while
   still lifting above the very dark page bg. */
wa-card[appearance="filled"] {
  --wa-color-neutral-fill-quiet: white;
}
.wa-dark wa-card[appearance="filled"] {
  --wa-color-neutral-fill-quiet: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
}

/* Callouts ship with --wa-border-radius-l (12px); that's louder than
   the rest of our chrome wants. Drop to medium (6px) so callouts
   sit at the same radius as form controls. Setting on the host
   cascades into the shadow DOM. */
wa-callout {
  --wa-panel-border-radius: var(--wa-border-radius-m);
}

wa-input::part(form-control-label),
wa-select::part(form-control-label),
wa-color-picker::part(form-control-label) {
  font-size: 14px;
  font-weight: 500;
}
wa-input.hex-input::part(input) {
  font-family: var(--wa-font-family-code);
}

/* ─── Base ─── */
/* Body bg + text use WA ladder tokens directly so cascade picks up
   look overrides inside body:has(.look-layout) — see
   _preview_pane.html.erb's <style> block. Outside that scope, vars
   resolve to CA chrome via :root. */
body {
  font-family: var(--wa-font-family-body);
  background: var(--wa-color-neutral-95);
  color: var(--wa-color-neutral-10);
  margin: 0;
  padding: 0;
  -webkit-font-smoothing: antialiased;
}
.wa-dark body {
  background: var(--wa-color-neutral-05);
  color: var(--wa-color-neutral-95);
}

/* Cera Round Pro alternate letterforms (ss01) — same rule fontawesome.com
   uses on its own marketing site. Excludes FA glyphs because Pro Kit
   renders them via font-feature-driven ligatures and toggling ss01 there
   could break icon paths (the i[class*=fa-] pre-i2svg form) or shadow-DOM
   svgs (.svg-inline--fa post-i2svg). */
body :not(:is(i[class*="fa-"], .svg-inline--fa)) {
  font-feature-settings: "ss01" on;
}

/* WA's wa-button shadow CSS sets font-weight: var(--wa-font-weight-action)
 * on [part=base], but the button's label is a Text node in the LIGHT DOM
 * (default-slot child). Per CSS Scoping rules, slotted content inherits
 * font-weight from its host's DOM context — NOT the shadow tree. So the
 * shadow's 700 never reaches the visible label, and buttons render their
 * text at the body's 400. Lift the host weight to the action token so
 * the slotted label inherits it. Affects every wa-button in the app. */
wa-button {
  font-weight: var(--wa-font-weight-action);
}
/* Sizing for FA icons inside labeled wa-buttons. Per project
 * convention (see WA-FEEDBACK.md), icons go in the default slot
 * inline with the label — `slot="start"`/`slot="end"` blockify
 * the SVG and break FA's baseline alignment.
 *
 * `--fa-width: auto` (FA Pro equivalent to `.fa-width-auto`) drops
 * the 1.25em fixed-width box so each glyph renders at its natural
 * viewBox width. Inline layout is preserved (no flex on `::part(label)`)
 * so FA's `vertical-align: -0.125em` keeps icons optically centered
 * on the text's x-height. Don't touch that.
 *
 * Excludes icon-only buttons (`aria-label`) which want the
 * fixed-width centered box. */
wa-button:not([aria-label]) > svg.svg-inline--fa {
  --fa-width: auto;
}

/* Icon-with-label spacing pattern (see DESIGN.md "Icon-with-label
 * spacing"). The class goes on the `<i>` directly — CSS can't tell
 * which side of the text the icon sits on (`:first-child` matches
 * regardless because text nodes don't count for sibling selectors),
 * so the intent has to be carried by the markup. One-sided margin
 * only — never both — so the centered slot stays on-axis.
 *
 *   .icon-leading   icon comes BEFORE the text   ("<icon> Label")
 *                   → margin-inline-end (gap toward the text).
 *   .icon-trailing  icon comes AFTER the text    ("Label <icon>")
 *                   → margin-inline-start (gap toward the text).
 */
.icon-leading {
  margin-inline-end: var(--wa-space-2xs);
}
.icon-trailing {
  margin-inline-start: var(--wa-space-2xs);
}
/* Inline form wrapper for a single wa-button inside a flex cluster
 * (wa-cluster, wa-stack, etc.). Rails' form_with renders a <form>
 * element which is block-level by default — display: contents makes
 * the form transparent for layout, so its child wa-button becomes
 * a direct flex item of the parent cluster. Used on social actions
 * (favorite, copy) and any other "single button as a form" surface. */
.wa-inline-form {
  display: contents;
}

/* ─── Top Nav ─── */
.ba-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  height: 52px;
  border-bottom: 1px solid var(--wa-color-neutral-90);
  background: var(--wa-color-neutral-95);
  position: sticky;
  top: 0;
  z-index: 100;
}
.wa-dark .ba-nav {
  border-bottom-color: var(--wa-color-neutral-20);
  background: var(--wa-color-neutral-05);
}
.ba-nav-left {
  display: flex;
  align-items: center;
  gap: 32px;
}
.ba-brand {
  font-family: var(--wa-font-family-heading);
  font-weight: 700;
  /* Title-case wordmark at body-medium step. Uppercase + letter-spacing
     was the FA-mark mimicry pattern from before the brand identity
     diverged — Title Case at the standard m-step reads as a product
     name in our own voice now. */
  font-size: var(--wa-font-size-m);
  color: var(--wa-color-neutral-10);
  text-decoration: none;
  display: flex;
  align-items: center;
  /* 6px sits the wordmark close to the palette glyph without crowding
     it — at fa-lg the icon's own internal padding adds visual breathing
     room beyond what the gap reads as. */
  gap: 6px;
  white-space: nowrap;
}
.wa-dark .ba-brand { color: var(--wa-color-neutral-95); }

/* Brand palette glyph — Pantone 224C pink everywhere by default
   (navbar + footer wordmarks share the same anchor). On a look
   show/edit page the look's brand-fill-loud wins so the wordmark
   anchors into the work in view; everywhere else the LA pink
   identity holds. body:has(.look-layout) is the same hook the
   look_theme_scope partial scopes its color tokens against, so the
   override fires exactly when the look's tokens are live. */
.ba-brand-icon {
  color: #EB6FBD;
}
body:has(.look-layout) .ba-brand-icon {
  color: var(--wa-color-brand-fill-loud);
}
.ba-nav-links {
  display: flex;
  gap: 4px;
}
.ba-nav-link {
  font-family: var(--wa-font-family-body);
  font-size: 14px;
  font-weight: 500;
  color: var(--wa-color-neutral-40);
  text-decoration: none;
  padding: 6px 12px;
  border-radius: 4px;
  transition: color 0.1s, background 0.1s;
  /* Right-nav entries (dark-mode toggle, account-menu trigger) render
     as <button> using this same class, so reset native button chrome
     and lock layout to inline-flex so anchors-with-icons + buttons
     paint identically. vertical-align: baseline overrides the UA's
     `middle` default on <button>, putting all entries on the same
     parent inline baseline as the anchor siblings. */
  background: transparent;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  vertical-align: baseline;
}
.wa-dark .ba-nav-link { color: var(--wa-color-neutral-60); }
.ba-nav-link:hover,
.ba-nav-link.active {
  color: var(--wa-color-neutral-10);
  background: white;
}
.wa-dark .ba-nav-link:hover,
.wa-dark .ba-nav-link.active {
  color: var(--wa-color-neutral-95);
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
}

/* ─── Main Content ─── */
.ba-main {
  max-width: 1200px;
  margin: 0 auto;
  padding: 32px 24px;
}

/* ─── Footer ─── */
/* Same surface as the body — the footer reads as the page's natural
   end, not a separate inverted slab. Semantic text + border tokens
   handle light/dark mode flip without explicit .wa-dark overrides:
   loud for the brand title, normal for column links (the things
   users actually click), quiet for body copy + legal + copyright,
   and surface-border for the divider rule. */
.ba-footer {
  color: var(--wa-color-text-quiet);
  padding: var(--wa-space-2xl) 0;
  margin-top: var(--wa-space-3xl);
}
.ba-footer-container {
  /* Mirror .ba-main's outer box so the footer's inner content edges
     line up with the gallery / my-looks page content. Was 1152px,
     which double-padded against .ba-main's 24px gutter and pulled
     footer content 24px in on each side. */
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--wa-space-l);
}
.ba-footer-main {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--wa-space-2xl);
  padding-bottom: var(--wa-space-l);
  border-bottom: 1px solid var(--wa-color-surface-border);
  margin-bottom: var(--wa-space-m);
}
.ba-footer-brand-block {
  flex: 1 1 320px;
  min-width: 0;
}
.ba-footer-brand {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--wa-font-family-heading);
  /* Matches fontawesome.com's footer headline (16px / 600 / cera-
     round-pro / margin-bottom 8px). Our `--wa-font-size-m`
     resolves to 16px, `--wa-space-xs` to 8px. */
  font-weight: 600;
  font-size: var(--wa-font-size-m);
  color: var(--wa-color-text-loud);
  margin: 0 0 var(--wa-space-xs) 0;
}
.ba-footer-tagline {
  max-width: 60ch;
  margin: 0;
  font-size: var(--wa-font-size-s);
  line-height: 1.5;
}
.ba-footer-col {
  display: flex;
  flex-direction: column;
  gap: var(--wa-space-2xs);
  flex: 0 0 auto;
}
.ba-footer-heading {
  font-family: var(--wa-font-family-heading);
  /* Matches fontawesome.com's footer column heading: 14px / 600 /
     uppercase / no extra tracking / 7-ish px margin. Our scale:
     `--wa-font-size-s` = 14px, `--wa-space-xs` = 8px. */
  font-size: var(--wa-font-size-s);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--wa-color-text-quiet);
  margin: 0 0 var(--wa-space-xs) 0;
}
.ba-footer-col a {
  color: var(--wa-color-text-normal);
  text-decoration: none;
  font-size: var(--wa-font-size-m);
}
.ba-footer-col a:hover {
  text-decoration: underline;
}
.ba-footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--wa-space-m);
  font-size: var(--wa-font-size-s);
}
.ba-footer-legal a {
  color: var(--wa-color-text-quiet);
  text-decoration: none;
}
.ba-footer-legal a:hover {
  text-decoration: underline;
}
.ba-footer-copyright {
  color: var(--wa-color-text-quiet);
}

/* ─── Tint Strip ─── */
.tint-strip {
  display: flex;
  height: 24px;
  border-radius: var(--wa-border-radius-m);
  overflow: hidden;
}
.tint-step {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ─── Unified color row — used by both /show and /edit via the
   shared _color_row.html.erb partial. The row is a wa-card; this
   class is the inner grid that lays out swatch + body. Card chrome
   (surface, border, padding, hover lift) comes from the wa-card
   filled-appearance defaults. ─── */
.color-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--wa-space-m);
}

/* Role select + (neutral-only) chroma-cap input split the row 50/50.
   When only the role select is present, :only-child pulls it to full
   width so the row never looks half-empty. */
.color-row-config {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--wa-space-xs);
  align-items: end;
}
.color-row-config > :only-child { grid-column: 1 / -1; }
.chroma-cap-input { min-width: 0; }
.chroma-cap-icon { cursor: pointer; color: var(--wa-color-text-quiet); }
/* /edit slots the cap icon into wa-input — let the input's own text
   cursor show through instead of forcing a click affordance the icon
   doesn't actually provide there. */
.chroma-cap-input .chroma-cap-icon { cursor: auto; }
/* Modal cap reads as plain metadata next to hex/OKLCH. Both wa-copy-
   button-wrapped codes render in `--wa-color-text-quiet`; match that so
   the cap doesn't read as the loudest item in the row. Cursor stays
   auto since the cap isn't itself a click target. */
.color-detail-cap { cursor: auto; color: var(--wa-color-text-quiet); }
.color-detail-cap .chroma-cap-icon { color: inherit; cursor: auto; }
/* wa-tooltip lives next to the chroma-cap input so the `for=` ref can
   anchor to it; it has no visual footprint of its own, so don't let it
   claim a grid column. */
.color-row-config > wa-tooltip { display: contents; }

/* Tooltips frequently sit inside monospace clusters (.color-detail-codes,
   .color-row-codes, copy-button readouts). The host element inherits the
   parent font-family, which then bleeds into the popup. Force body font
   on every tooltip so messages always read as prose, regardless of the
   surrounding context. ::part(body) covers WA's shadow-DOM rendering. */
wa-tooltip,
wa-tooltip::part(body) {
  font-family: var(--wa-font-family-body);
}
.color-row-swatch {
  /* Token contract for the swatch surface — same values shared by
     /show's bare-div swatch (this rule), /show's bordered card
     swatch (.show-color-card variant below), and the picker's
     shadow-DOM trigger (which inherits these through the shadow
     boundary). Override per-host if a context wants a different
     border or radius. */
  --swatch-border: 1px solid rgba(0, 0, 0, 0.08);
  --swatch-radius: 6px;
  /* 54×54 matches what app-oklch-picker[size=large] renders on /edit:
     its trigger uses var(--wa-form-control-height) which evaluates to
     2.7em — at the picker's 1.25rem (20px) host font-size, that's 54px.
     Hardcoded here instead of using the WA token because /show's
     swatch is a bare div, not a wa-* form control, so it doesn't sit
     in WA's em-based scaling chain. */
  width: 54px;
  height: 54px;
  border-radius: var(--swatch-radius);
  flex-shrink: 0;
}
/* The picker swatch occupies the same column as /show's plain
   swatch. app-oklch-picker renders its own trigger inside shadow
   DOM; the host element doesn't need explicit dimensions or
   borders. */
app-oklch-picker.color-row-swatch {
  width: auto;
  height: auto;
  border: none;
}

.contrast-label {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--wa-color-text-quiet);
}
.contrast-on-white {
  display: inline-block;
  padding: 1px 6px;
  background: #fff;
  border: 1px solid var(--wa-color-surface-border);
  border-radius: 4px;
  color: var(--wa-color-text-normal);
  font-weight: 600;
  font-size: 12px;
}
.contrast-on-black {
  display: inline-block;
  padding: 1px 6px;
  background: var(--wa-color-text-normal);
  border: 1px solid var(--wa-color-text-normal);
  border-radius: 4px;
  color: var(--wa-color-surface-raised);
  font-weight: 600;
  font-size: 12px;
}

/* <code> inside the codes row always renders flat mono — wa-caption-*
   utility classes flip font-family to body, and WA's default inline-
   code styling adds a gray pill (subtle fill + padding + 3px radius).
   Override both so hex + OKLCH read as ordinary text. The chroma-cap
   indicator now rides inline inside the show-card h5, so the same
   reset applies there to keep the cap value visually identical. */
.color-row-codes code,
.show-color-card h5 code {
  font-family: var(--wa-font-family-code);
  background: transparent;
  padding: 0;
  border-radius: 0;
}
/* Inline middot between role label and chroma-cap indicator gets a
   little breathing room on both sides — the surrounding inline flow
   only gives a single character space, which reads cramped. */
.chroma-cap-sep { margin: 0 var(--wa-space-2xs); }


.head-sep {
  color: var(--wa-color-text-quiet);
  user-select: none;
  line-height: 1;
}

.mini-swatch {
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: 4px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  flex-shrink: 0;
  vertical-align: middle;
}

/* ─── /show color cards + detail modal ─── *
 * Each color on /show is a clickable wa-card that opens its detail
 * modal. Card visuals share .color-row / .color-row-swatch / .tint-strip
 * with /edit's _color_row partial (defined above). What's new here is
 * just the card-level affordance: cursor, hover lift, focus ring,
 * since the whole card is now a button.
 */
.show-color-card {
  transition: transform 120ms ease-out, box-shadow 120ms ease-out;
}
.show-color-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--wa-shadow-m, 0 4px 12px rgba(0, 0, 0, 0.08));
}
.show-color-card:focus-visible {
  outline: 2px solid var(--wa-color-brand-fill-loud);
  outline-offset: 2px;
}
/* Subtle 1px ring around the swatch — reads the --swatch-border
 * token from .color-row-swatch above, with `box-sizing: border-box`
 * so the 54×54 outer footprint stays exactly 54×54 (border eats
 * inward instead of pushing the box outward). */
.show-color-card .color-row-swatch {
  border: var(--swatch-border);
  box-sizing: border-box;
}

/* Detail modal — fits the full ladder + base info comfortably. */
.color-detail-dialog {
  --width: min(720px, 92vw);
}
.color-detail-header {
  display: flex;
  gap: var(--wa-space-l);
  align-items: flex-start;
  padding-bottom: var(--wa-space-m);
  border-bottom: 1px solid var(--wa-color-surface-border);
  margin-bottom: var(--wa-space-m);
}
.color-detail-swatch {
  width: 80px;
  height: 80px;
  border-radius: var(--wa-border-radius-l);
  border: 2px solid rgba(0, 0, 0, 0.08);
  flex-shrink: 0;
}
.color-detail-meta {
  display: flex;
  flex-direction: column;
  gap: var(--wa-space-xs);
  min-width: 0;
}
.color-detail-codes {
  font-family: var(--wa-font-family-code);
  display: flex;
  align-items: center;
  gap: var(--wa-space-xs);
  flex-wrap: wrap;
}
.color-detail-codes code { font-family: inherit; }

/* WA's longform CSS gives bare <code> a subtle gray pill (fill +
   padding + 3px radius) and wa-caption-2xs flips font-family to body.
   The codes here are already wrapped in wa-copy-button, which carries
   the click affordance; we want them to read as ordinary code text,
   so strip the pill and force monospace. */
.color-detail-codes code,
.color-detail-table code {
  font-family: var(--wa-font-family-code);
  background: transparent;
  padding: 0;
  border-radius: 0;
}
/* The whole code-wrapped wa-copy-button is the click target; show
   the pointer so the affordance is unambiguous. */
.color-detail-codes wa-copy-button,
.color-detail-table wa-copy-button {
  cursor: pointer;
}
/* Single-cell grid lets the outgoing + incoming slides overlap during
   navigation, with the body sizing to whichever is larger. overflow:
   hidden clips the side translation so neighboring slides never poke
   out past the dialog edges. */
.color-detail-body {
  display: grid;
  overflow: hidden;
}
.color-detail-body > .color-detail-slide {
  grid-column: 1;
  grid-row: 1;
  /* Keep MUST match SLIDE_MS in color_detail_controller. */
  transition: transform 260ms cubic-bezier(0.4, 0, 0.2, 1),
              opacity 260ms ease-out;
}
/* Subtle horizontal nudge — feels like a deck flip rather than a full
   carousel sweep, which fits a modal's tight footprint. */
.color-detail-slide.from-right { transform: translateX(40px); opacity: 0; }
.color-detail-slide.from-left  { transform: translateX(-40px); opacity: 0; }
.color-detail-slide.to-left    { transform: translateX(-40px); opacity: 0; }
.color-detail-slide.to-right   { transform: translateX(40px); opacity: 0; }

.color-detail-table-scroll {
  overflow-x: auto;
}
.color-detail-table {
  width: 100%;
  border-collapse: collapse;
}
.color-detail-table th,
.color-detail-table td {
  padding: var(--wa-space-2xs) var(--wa-space-xs);
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
}
.color-detail-table thead th {
  color: var(--wa-color-text-quiet);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  border-bottom: 1px solid var(--wa-color-surface-border);
}
.color-detail-table tbody tr + tr { border-top: 1px solid var(--wa-color-surface-border); }
/* On narrow viewports the OKLCH column is the bulky one (~150px for
   "oklch(0.71 0.176 344)"). Hide it so swatch + step + hex + WCAG
   columns all fit on one line without wrapping or horizontal overflow.
   The modal header still shows the base color's OKLCH; per-step math
   is desktop-only, which matches the "math is for inspecting" framing. */
@media (max-width: 600px) {
  .color-detail-table th:nth-child(4),
  .color-detail-table td:nth-child(4),
  .color-detail-codes-oklch {
    display: none;
  }
}
/* Strip the line-box leading inside the swatch column so the
   inline-block .mini-swatch centers true to the row's vertical
   midpoint instead of sitting ~1px below it. */
.color-detail-cell-swatch { line-height: 0; }
.color-detail-table tbody tr.color-detail-row-base {
  background: var(--wa-color-neutral-fill-quiet);
}

/* ─── Code Block ─── */
.code-block {
  background: var(--wa-color-text-normal);
  color: var(--wa-color-surface-raised);
  padding: 16px;
  border-radius: 8px;
  font-family: var(--wa-font-family-code);
  font-size: 13px;
  line-height: 1.6;
  white-space: pre;
  overflow-x: auto;
  margin: 0;
}

/* ─── Role Assignment (Create Page) ─── */
.role-assignment-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.role-assignment-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--wa-color-surface-raised);
  border-radius: 8px;
  border: 1px solid var(--wa-color-surface-border);
}
.role-assignment-swatch {
  width: 36px;
  height: 36px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  flex-shrink: 0;
}
@media (max-width: 600px) {
  .role-assignment-row {
    flex-wrap: wrap;
  }
}

/* ─── Gallery Grid ─── */
.look-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 24px;
}

/* ─── Unified Look layout — used by both /show and /edit. Three
   equal columns: info (header, name, source images, actions),
   colors (the saved-colors-grid stack), and rail (look card +
   components sample). Same shell on both pages so view ↔ edit feels
   like the same surface with different affordances. ─── */
.look-layout {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Row-gap one step tighter than column-gap (l vs xl) so /edit's
     full-width title sits closer to the content row than the columns
     sit to each other — the title's role is to introduce the row, not
     headline a separate section. /show has only one grid row visually
     so row-gap is moot there. */
  column-gap: var(--wa-space-xl);
  row-gap: var(--wa-space-l);
  align-items: start;
}
/* /edit's full-width title banner — first row of the look-layout
   grid, spans all 3 columns. Sits above the preview card / colors /
   components_sample. */
.look-edit-title {
  grid-column: 1 / -1;
  margin: 0;
}
.look-rail {
  position: sticky;
  /* 52px navbar + 24px breathing room */
  top: 76px;
  display: flex;
  flex-direction: column;
  gap: var(--wa-space-m);
}
/* /show's rail packs more items (card + meta + actions + image) than
   /edit's (card + components), so tighten the gap to keep the column
   visually unified. Scoped to .look-layout-show; /edit keeps the m gap. */
.look-layout-show .look-rail {
  gap: var(--wa-space-s);
}
/* Components sample sits in its own column on /show + /edit — kill
   its default top margin so it aligns with the card and color rows
   at the top of the layout. */
.look-components .components-sample,
#look-components-container .components-sample {
  margin-top: 0;
}
@media (max-width: 1024px) {
  .look-layout {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .look-rail {
    grid-column: 1 / -1;
    position: static;
  }
}
@media (max-width: 768px) {
  .look-layout {
    grid-template-columns: 1fr;
  }
  .look-rail { grid-column: auto; }
}

/* Let long URLs inside alerts break mid-character instead of blowing out the layout. */
wa-alert code { overflow-wrap: anywhere; }

/* ─── /edit staged-edit chrome ─── *
 * The .look-edit-shell wraps the whole edit page (recovery banners +
 * .look-layout + the discard-changes dialog). It establishes the
 * controller scope for look-edit. Nothing else relies on it for
 * layout; it's a transparent grouping element.
 *
 * .look-edit-controls is the col-1 cluster (undo/redo, Save/Discard,
 * Publish). The cluster aligns to the right edge of .look-info via
 * the parent's wa-align-items-end, matching the existing rhythm of the
 * action cluster it replaced.
 *
 * .look-edit-banner stacks above the layout. wa-callout's default
 * margin is fine; we just add a small gap below so it doesn't touch
 * the H1 when the layout begins. */
.look-edit-shell {
  display: contents;
}
.look-edit-banner {
  /* No `display:` here — wa-callout's :host { display: flex } puts the
   * icon in a column to the left of the message. Setting display:block
   * overrides that and stacks the slotted icon ABOVE the body. */
  margin-bottom: var(--wa-space-m);
}
/* wa-callout sets its own `display` in author CSS, which overrides the
 * UA stylesheet's `[hidden] { display: none }`. Force it so the
 * controller's hidden-toggle actually hides/shows the banners. */
.look-edit-banner[hidden],
.export-unsaved-banner[hidden] {
  display: none !important;
}
/* Pin the slotted icon to the top of the row instead of the default
 * vertical centering — for multi-line banners (title + body + buttons)
 * a centered icon floats next to the body, which reads as misaligned. */
.look-edit-banner::part(icon),
.export-unsaved-banner::part(icon),
.look-edit-save-error::part(icon) {
  align-self: flex-start;
}
/* Stack heading + paragraph + buttons with equal medium gaps. wa-callout's
 * default-slot wrapper (::part(message)) is `display: block`; flipping
 * it to a flex column with gap gives consistent rhythm without per-
 * child margins to maintain. */
.look-edit-banner::part(message) {
  display: flex;
  flex-direction: column;
  gap: var(--wa-space-m);
}
/* Reset margins on slotted h1/p — WA's longform CSS adds margins to
 * bare elements, which would compete with the gap above. */
.look-edit-banner h1,
.look-edit-banner p {
  margin: 0;
}

.export-unsaved-banner {
  margin-bottom: var(--wa-space-s);
}
.look-edit-controls {
  width: 100%;
}
/* wa-button styles its slot="start" host with an explicit display, which
 * overrides the UA stylesheet's `[hidden] { display: none }`. Same
 * issue we hit on wa-callout. Force the toggle so the save-state icon
 * actually disappears outside the just-saved flash. */
.look-edit-controls wa-button [slot="start"][hidden] {
  display: none !important;
}
/* Save error callout sits directly under the Save/Discard cluster. */
.look-edit-save-error {
  width: 100%;
}

/* ─── OKLCH editor trio (color cards on the edit page) ─── */
/* 3-column grid: each cell shows an axis label (L/C/H) inline-prefixed
   to a small wa-input[type=number]. Replaces the single hex wa-input
   with three look-axis fields. Native arrow keys nudge each axis
   per the input's `step` (L: 0.01, C: 0.005, H: 1°). */
.lch-trio {
  display: flex;
  gap: 10px;
}
.lch-cell {
  display: flex;
  align-items: center;
  gap: 4px;
}
.lch-cell .axis-label {
  font-family: var(--wa-font-family-code);
  font-size: 10px;
  font-weight: 500;
  color: var(--wa-color-text-quiet);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.lch-cell wa-input {
  width: 52px;
  /* Tighten WA's default 1em horizontal padding so a 3-digit value
     fits without overflow. */
  --wa-form-control-padding-inline: 4px;
}
.lch-cell wa-input::part(input) {
  text-align: center;
  font-family: var(--wa-font-family-code);
  font-size: 12px;
}
/* Hide the native browser number-spinner — arrow keys still nudge
   by the input's `step` attribute. */
.lch-cell wa-input::part(input)::-webkit-outer-spin-button,
.lch-cell wa-input::part(input)::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.lch-cell wa-input::part(input) {
  -moz-appearance: textfield;
}

/* ─── Components sample (edit-page right column, under look card) ─── */
/* Card surface uses the same synthetic "step ~97" tint as the form
   cards (see wa-card[appearance="filled"] above) — look neutral-95
   mixed with white in OKLAB so the card lifts one perceptual step
   above the body bg. Dark mode mixes neutral-05 with a touch of white
   for the same lift in the inverted direction. The active look's
   neutral hue character carries through both mixes via the bridge. */
.components-sample {
  background: white;
  /* Match wa-card[appearance="filled"]: 12px radius, transparent
     border (the lift comes from the shadow), --wa-shadow-s for the
     standard subtle drop shadow. */
  border: 1px solid transparent;
  border-radius: 12px;
  box-shadow: var(--wa-shadow-s);
  padding: 20px;
  margin-top: 16px;
}
.wa-dark .components-sample {
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
}
/* Eyebrow label pattern — uppercase tracking-wide micro-labels above
   sections / cards. Size + quiet color come from wa-caption-* on the
   element; this class supplies the rest. Used for "PICK A STYLE",
   "COMPONENT PREVIEW", "BUTTONS" / "CALLOUTS" / "BADGES" etc. The
   eyebrow class is body-font (matches wa-caption defaults); add a
   custom class on top if you need a code-font / decorated variant.
   Bottom margin is whatever the consumer needs — set inline. */
.eyebrow {
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0;
}
.cs-section { margin-bottom: var(--wa-space-m); }
.cs-section:last-child { margin-bottom: 0; }
.cs-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.cs-callouts {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.cs-callouts wa-callout {
  font-size: 12px;
  padding: 8px 10px;
}
.cs-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.cs-controls {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cs-controls wa-progress-bar { --height: 6px; }

/* ─── Gallery Cards ─── */
.card-colors {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 12px 0;
}
/* Large preview cards (/show + /edit) get 16px body padding from
   --spacing: var(--wa-space-m); the gallery's extra 12px would
   stack on top and inset the chroma 28px from the card edge —
   visibly more than the color cards (17px) or components-sample
   (21px) it sits next to on /edit, and visibly more breathing
   room than the body padding intends on /show. Drop card-colors's
   own padding so the chroma rides on the body padding alone. */
.look-card-large .card-colors {
  padding: 0;
}
.card-row-1, .card-row-2, .card-row-3 { display: flex; gap: 4px; }
.card-row-1 > * { flex: 1; border-radius: 4px; }
/* Row 1 width ratios depend on which of brand/accent/neutral are present.
   Brand alone fills 100% via the flex:1 rule above. */
/* brand + neutral only = 90/10 */
.card-row-1:has(.card-block-neutral):not(:has(.card-block-accent)) > .card-block-brand { flex: 9; }
.card-row-1:has(.card-block-neutral):not(:has(.card-block-accent)) > .card-block-neutral { flex: 1; }
/* brand + accent only = 70/30 */
.card-row-1:has(.card-block-accent):not(:has(.card-block-neutral)) > .card-block-brand { flex: 7; }
.card-row-1:has(.card-block-accent):not(:has(.card-block-neutral)) > .card-block-accent { flex: 3; }
/* brand + accent + neutral = 60/30/10 */
.card-row-1:has(.card-block-accent):has(.card-block-neutral) > .card-block-brand { flex: 6; }
.card-row-1:has(.card-block-accent):has(.card-block-neutral) > .card-block-accent { flex: 3; }
.card-row-1:has(.card-block-accent):has(.card-block-neutral) > .card-block-neutral { flex: 1; }
/* no brand: accent + neutral = 70/30. accent becomes the hero when brand
   is absent, matching the brand+accent ratio spirit. accent-only and
   neutral-only just fill 100% via the default flex:1 rule above. */
.card-row-1:not(:has(.card-block-brand)):has(.card-block-accent):has(.card-block-neutral) > .card-block-accent { flex: 7; }
.card-row-1:not(:has(.card-block-brand)):has(.card-block-accent):has(.card-block-neutral) > .card-block-neutral { flex: 3; }
.card-row-2 > *, .card-row-3 > * { flex: 1; border-radius: 4px; }

/* Row heights by visible-row count. Total swatch area stays 170px. */
.card-colors-1row > :nth-child(1) > * { height: 170px; }
.card-colors-2row > :nth-child(1) > * { height: 122px; }
.card-colors-2row > :nth-child(2) > * { height: 44px; }
.card-colors-3row > :nth-child(1) > * { height: 106px; }
.card-colors-3row > :nth-child(2) > * { height: 38px; }
.card-colors-3row > :nth-child(3) > * { height: 18px; }
/* Empty-state card — same 170px footprint as a populated 1-row card
   so the clickable surface on an empty look matches every other
   card in the gallery. Dashed outline + bg tile hints "no colors
   yet" without stealing attention from the link. */
.card-block-empty {
  background: var(--wa-color-surface-default);
  border: 1px dashed var(--wa-color-surface-border);
}
/* Create-Look ghost card variant — center the brand palette glyph in
   the empty swatch slot and pull the heading down to the same low
   contrast step (overrides .card-name-text's default quiet color).
   Whispers "a Look could live here" without competing with populated
   cards; hover paints both to the loud brand color so the CTA
   resolves into a real entry point. Light mode uses neutral-40 (a
   medium-dark gray reading quiet on the page surface); dark mode
   inverts to neutral-60 around the ladder midpoint so the same
   "quiet but legible" perception holds against the inverted bg. */
.card-block-create {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 64px;
  color: var(--wa-color-neutral-60);
  transition: color 150ms ease-out;
}
wa-card:has(.card-block-create) .card-name-text {
  color: var(--wa-color-neutral-60);
}
.wa-dark .card-block-create,
.wa-dark wa-card:has(.card-block-create) .card-name-text {
  color: var(--wa-color-neutral-40);
}
/* :has(.card-block-create) gates the title-color paint to Create-CTA
   cards only — populated gallery cards keep their quiet text on hover.
   The icon rule doesn't need the gate because .card-block-create only
   exists on the CTA variant. */
.card-link:hover .card-block-create,
.card-link:has(.card-block-create):hover .card-name-text {
  color: var(--wa-color-brand-fill-loud);
}
/* .card-link — system-wide marker for "this is the wa-card's primary
   content link." Apply to the <a> that wraps the card's main body
   when the whole card surface should act as the click target.
   Combined with the hover rule below, this is how every clickable
   card in the app gets its lift affordance. */
.card-link { display: block; }

/* Whole-surface clickable card lift. Two patterns count as
   "whole-surface clickable":
     1. wa-card with a direct child <a class="card-link"> (the link
        wraps the card content)
     2. wa-card[role="button"] (the wa-card itself is the button)
   Cards that have only overlay buttons (delete, eyedropper) but no
   primary content link / button-role don't match either selector
   and stay static — they aren't whole-surface clickable. Lift =
   translateY(-2px) + shadow ramp from --wa-shadow-s (wa-card's
   resting elevation) to --wa-shadow-xl. */
wa-card:has(> a.card-link),
wa-card[role="button"] {
  transition: transform 120ms ease-out, box-shadow 120ms ease-out;
}
wa-card:has(> a.card-link):hover,
wa-card[role="button"]:hover {
  transform: translateY(-2px);
  box-shadow: var(--wa-shadow-xl);
}
/* Active state: card "presses back down" to its baseline Y while
   keeping the elevated shadow. The 2px drop reads as a tactile push
   without losing the lifted-card feel. */
wa-card:has(> a.card-link):active,
wa-card[role="button"]:active {
  transform: translateY(0);
}
/* /show action rows — buttons share 100% of the row width but keep
   content-proportional sizes (longer labels are naturally wider).
   `flex: 1 1 auto` starts each item at its content width and grows
   them by an equal share of the remaining row space. The wa-button
   inside any wrapper (wa-copy-button, turbo-frame, form) is set to
   width: 100% so it fills its grown wrapper. */
.action-row {
  display: flex;
  gap: var(--wa-space-xs);
}
.action-row > * {
  flex: 1 1 auto;
}
.action-row wa-button {
  width: 100%;
}

.card-name {
  padding: var(--wa-space-s) var(--wa-space-m);
  /* Reserve space for two lines of the heading so the box height
     stays uniform across a gallery row, regardless of which titles
     wrap to two lines and which fit on one. */
  height: calc(var(--wa-font-size-l) * 1.15 * 2 + var(--wa-space-s) * 2);
  display: flex;
  align-items: center;
}
/* When the card has social actions overlaying the right side, reserve
   horizontal space on the title so a long name line-clamps before
   running under the icons. Large cards have three icons (heart, copy,
   share URL) so they need more room than the regular two. */
/* Regular gallery cards: icons absolute-overlay the title's right side;
   reserve title space so a long name line-clamps before running under
   the cluster. Large cards stack title + counts inside .card-name and
   get their own width treatment below. */
wa-card:has(.card-actions) .card-name:not(.card-name-large) {
  padding-right: 65px;
}
/* Large cards: three icons (share + copy + heart) overlay the title
   row; reserve a bit more room than regular's two-icon cluster.
   Padding top/bottom are 0 — the card body's own 16px padding handles
   vertical rhythm; the title sits flush with the chroma above and the
   card edge below. */
.card-name-large {
  padding-top: var(--wa-space-m);
  padding-right: 75px;
  padding-bottom: 0;
  padding-left: 0;
}

/* Card + its meta caption render as a unit on /show + /edit. The
   .look-rail flex column puts var(--wa-space-m) between top-level
   children; this wrapper tightens the gap between the card and the
   line that describes it ("Created N · Copied from X") so the caption
   reads as belonging to the card above. */
.look-card-block {
  display: flex;
  flex-direction: column;
  gap: var(--wa-space-2xs);
}
.card-meta,
.card-counts {
  margin: 0;
  color: var(--wa-color-text-quiet);
}

/* Large-card variant of .card-name — title plus a counts sub-line
   stacked underneath. Switches from the regular's fixed 2-title-line
   height + center-align to a column flex with min-height: 72px so the
   block keeps a consistent footprint when counts are hidden or short.
   justify-content: center vertically centers the title (+ counts when
   present) inside that 72px floor; the block grows past 72 when
   content is taller (e.g. a 2-line title + counts row). The
   padding-right rule above reserves icon space; gap: 0 keeps title
   and counts butted directly together. */
.card-name-large {
  height: auto;
  min-height: 60px;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
}
/* Edit-mode card-name: hosts the look-name form input where the
   title would live on /show. The input takes the full card width;
   the heading-family font keeps the visual continuity with the
   gallery card title it replaces. Top padding matches the wa-card
   body padding (16px) so the gap between the chroma and the input
   reads as the same rhythm as everything else inside the card.
   min-height: 0 cancels card-name-large's 72px floor — the input
   has its own intrinsic height; no need to reserve title-row space. */
.card-name-edit {
  padding: var(--wa-space-m) 0 0 0;
  min-height: 0;
}
.card-name-input {
  width: 100%;
  font-family: var(--wa-font-family-heading);
}
/* Large preview cards use the loud text color on the title — the
   gallery's quiet color reads correctly at small sizes but feels
   washed out as a hero title on /show + /edit. */
.card-name-large .card-name-text {
  color: var(--wa-color-text-loud);
}
/* Heading element — the wa-heading-* utility set inline on the
   element in _look_card.html.erb supplies font-family + weight +
   size; this class only adds layout constraints (margin reset,
   line-clamp ellipsis, line-height for the 2-line height calc). */
.card-name-text {
  margin: 0;
  color: var(--wa-color-text-quiet);
  line-height: 1.15;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-word;
  width: 100%;
}

/* ─── Devise / auth screens ───────────────────────────────────────────
 * Sign-in / sign-up / forgot-password / reset-password / account
 * settings all render the same .auth-card shape: centered, narrow,
 * filled wa-card with a heading + form + footer links. The card sits
 * inside .ba-main's normal grid; it just constrains its own width
 * and centers itself horizontally instead of inventing a new layout.
 */
.auth-card {
  max-width: 420px;
  margin: var(--wa-space-2xl) auto;
}
.auth-title {
  margin: 0 0 var(--wa-space-l) 0;
}
/* Heading-side icon picks up brand pink so it ties to the wa-button
 * accent below — the heading + CTA share a single visual accent. */
.auth-title-icon {
  color: var(--wa-color-brand-fill-loud);
  margin-right: var(--wa-space-xs);
}
.auth-blurb {
  color: var(--wa-color-text-quiet);
  margin: 0 0 var(--wa-space-s) 0;
}
.auth-card wa-button[type="submit"],
.auth-card .auth-secondary-btn {
  width: 100%;
}
/* For forms where every visible field is required (sign-in, forgot-
 * password, reset-password), the per-label asterisks read as noise.
 * Forms with mixed required + optional fields (sign-up, account-
 * settings) keep their indicators so the optional fields are visually
 * distinguishable. Empty the WA token instead of dropping the
 * `required` attribute — client-side validation still gates submit. */
.auth-form-no-asterisk wa-input {
  --wa-form-control-required-content: "";
}
/* Suffix icons inside the sign-in inputs read as quiet decoration —
 * the input value is the actual content. Drop them well below the
 * value's contrast using the neutral-70 step (~#aeaeae) so the icons
 * recede; pure --wa-color-text-quiet was still too loud here. */
.auth-input-icon {
  color: var(--wa-color-neutral-70);
}
/* One step smaller than WA's default hint size (--wa-font-size-smaller
 * = 1em / 1.125). The hint serves as supporting metadata under the
 * input — value typography wins, hint recedes. */
.auth-form wa-input::part(hint) {
  font-size: var(--wa-font-size-s);
}
/* Remember Me + Forgot Password share a single row, so the cluster
 * needs centered cross-axis alignment (otherwise the link sits at the
 * top while the wa-checkbox baseline drifts a few px lower). */
.auth-row-options {
  align-items: center;
}
/* Visual separation between the primary form and the secondary
 * "Create Account" affordance. wa-divider's host margin defaults
 * are too tight inside the auth-card column. */
.auth-divider {
  margin: var(--wa-space-l) 0;
}
.auth-errors {
  margin: 0;
  padding-left: var(--wa-space-l);
}
.auth-links {
  margin-top: var(--wa-space-l);
  text-align: center;
  color: var(--wa-color-text-quiet);
  font-size: var(--wa-font-size-s);
}
.auth-link {
  color: var(--wa-color-brand-fill-loud);
  text-decoration: none;
}
.auth-link:hover {
  text-decoration: underline;
}
.auth-sep {
  margin: 0 var(--wa-space-2xs);
  color: var(--wa-color-text-quiet);
}
/* ─── Responsive ─── */
@media (max-width: 768px) {
  .ba-nav-links { display: none; }
}
