/* 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 --la-* 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);
  /* Softer quiet text — one step less contrast than WA default (-40). */
  --wa-color-text-quiet: var(--wa-color-neutral-50);
  --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);
  /* Softer quiet text — one step less contrast than WA default (-60). */
  --wa-color-text-quiet: var(--wa-color-neutral-50);
  /* 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;
}

/* In-card meta row — line 1 of the card layout. Three action
   buttons on the left (icon + optional count, single click target),
   owner @handle on the right. Caption-s text, quiet color at rest;
   hover or favorited paints the whole unit brand. Lives as a sibling
   of the .card-link sleeves wrapping the swatches + title.

   Horizontal padding matches the swatch block's inset (12px on
   regular gallery cards; 0 on large preview cards where wa-card's
   own --spacing handles the inset). Keeps left + right edges of
   the row flush with the color blocks above. */
/* In-card meta row — line 1 of the card layout. Action buttons flow
   inline on the left (icon + count read as text, with the
   document's natural whitespace between elements); the handle is
   pinned to the right via margin-left: auto. align-items: center
   shares the same vertical line across the heart, shirt, paper-
   plane, count digits, and @handle — independent of FA glyphs'
   intrinsic viewBox differences. */
.card-meta {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  /* Vertical 12px provides the inter-section gap between the swatches
     above and the title below; horizontal is 0 because wa-card's
     --spacing handles left/right body padding for both sizes. */
  padding: var(--wa-space-s) 0;
  color: var(--wa-color-text-quiet);
}
/* Action buttons + the signed-out <a> heart all wear .card-meta-action.
   This block neutralizes WA's <button> reset (which forces height:
   38px / line-height to match it / padding / border / radius) so all
   three actions render as plain inline-text-style targets at their
   content's natural height — gives the row a single shared
   vertical line. */
.card-meta-action {
  display: inline-flex;
  align-items: center;
  /* 2px between the icon and its count inside each button. */
  gap: var(--wa-space-3xs);
  height: auto;
  /* Right-padding (not margin) builds the inter-button gap so the
     empty space between icons is part of the click target — clicks
     in the 8px gap to the right of an icon hit that button.
     Padding-left is 0 so the first icon still aligns flush with the
     swatch edge. No vertical padding: keeps the row height pinned
     to the SVG content. */
  padding: 0 var(--wa-space-xs) 0 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  /* line-height: 1 pins the action button's box to its content (the
     14px FA SVG). Must come AFTER `font: inherit` because the font
     shorthand expands to include line-height — otherwise it resets
     to the inherited 16.8px and the signed-out <a> heart picks up a
     taller box than the <button> remix/share, causing the icons to
     sit at slightly different vertical positions. */
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  text-decoration: none;
  transition: color 150ms ease-out;
}
/* Brand-tinted on hover; favorited state pins the heart unit brand
   without needing hover. Same treatment for all three action types
   so the row reads as one consistent interactive surface. */
/* Icons sit one step softer than the surrounding caption text —
   neutral-70 light / -30 dark — so they read as quiet ghost cues
   that let brand pink do the talking on hover/active. Restores the
   pre-refactor ".card-action" tone. */
.card-meta-action {
  color: var(--wa-color-neutral-70);
}
.wa-dark .card-meta-action {
  color: var(--wa-color-neutral-30);
}
.card-meta-action:hover,
.card-meta-action-favorite.is-favorited {
  color: var(--wa-color-brand-fill-loud);
}
/* Slight tilt on the share paper-plane gives the icon some "lift"
   visual character — it reads as in-motion rather than parked. The
   rotation pushes the icon's bottom-right corner toward the count
   digit, so this button gets one extra step of icon→count spacing
   (--wa-space-2xs vs the default --wa-space-3xs) to compensate. */
.card-meta-action-share .fa-paper-plane,
.card-meta-action-share svg.fa-paper-plane {
  transform: rotate(20deg);
}
.card-meta-action-share {
  gap: var(--wa-space-2xs);
}
.card-meta-handle {
  margin-left: auto;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex-shrink: 1;
}

/* Favorite turbo-frame goes transparent for layout (same trick as
 * .wa-inline-form) so the favorite button inside acts as a direct
 * child of its parent cluster — keeps the button on the same
 * baseline as its siblings in .card-meta. */
.favorite-frame {
  display: contents;
}

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

/* Generic prev/next nav for paginated dialogs (color-detail,
   image-preview). 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 — any dialog that
   wants this nav must be on the bare-dialog selector lists above. */
.dialog-side-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;
}
.dialog-side-nav.prev { left: -44px; }
.dialog-side-nav.next { right: -44px; }
.dialog-side-nav:hover {
  --fa-secondary-color: var(--wa-color-text-normal);
}
.dialog-side-nav.prev:hover { transform: translateY(-50%) translateX(-2px); }
.dialog-side-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;
  user-select: none;
}
/* Crosshair scoped to the /edit sampler where the user clicks the
   image to pick a color. /show reuses the same .image-sample-* chrome
   for the read-only preview modal but has nothing to sample, so the
   default cursor reads correctly there (the chevrons are the only
   click affordance, and they have their own pointer cursor). */
.image-sample-image-wrap img[data-image-sample-target="image"] {
  cursor: crosshair;
}
/* 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;
}

/* ─── App shell (wa-page) ─── */
/* Layout chrome lives inside <wa-page> now — header, navigation,
   main, footer slots. wa-page handles the responsive collapse to a
   left-hand nav drawer below 768px on its own. */

/* Align the project's surface-default with the body color. WA paints
   wa-page (and a handful of other "page surface" treatments) using
   --wa-color-surface-default, which ships as #ffffff in WA's defaults.
   The body already uses --wa-color-neutral-95 (off-white) in light
   and --wa-color-neutral-05 (off-black) in dark. Without this override
   the two disagree — wa-page paints pure white over the body's
   off-white. Pointing the token at the body's value lines them up
   everywhere WA reads "page surface" without us hand-styling each
   slot. Cards / dialogs / other "raised" surfaces use surface-raised,
   which we leave alone, so they keep popping against the page in both
   modes. */
:root {
  --wa-color-surface-default: var(--wa-color-neutral-95);
  /* WA chains --wa-form-control-background-color to surface-default by
     default. Now that surface-default is off-white (matching the page),
     form controls would lose their pop. Re-point form controls at
     surface-raised (white in light, dark-elevated in dark) so wa-input
     / wa-select / wa-textarea / wa-file-input read as raised fields
     above the page surface in both modes. */
  --wa-form-control-background-color: var(--wa-color-surface-raised);
}
.wa-dark {
  --wa-color-surface-default: var(--wa-color-neutral-05);
}

/* wa-page's body grid is `[menu, main, aside]` with `--menu-width: auto`
   by default. Even with an empty `slot="menu"`, wa-page's fallback
   `<nav>` content gives the menu column a 32px intrinsic width — which
   pushes the main column 32px right of the footer (footer sits outside
   the body grid). Collapsing the menu column to 0 puts main and footer
   on the same horizontal axis. The aside is already 0 (no slot
   content), so this single override aligns both edges. */
.la-shell {
  --menu-width: 0;
}

/* Brand wordmark inside the header slot. Title-case at the body-m
   step reads as a product name in our own voice; the palette glyph
   to its left picks up Pantone 224C pink by default (or the active
   Look's brand-fill-loud on show/edit, see .la-brand-icon below). */
.la-brand {
  font-family: var(--wa-font-family-heading);
  font-weight: 700;
  font-size: var(--wa-font-size-m);
  color: var(--wa-color-text-loud);
  text-decoration: none;
  display: inline-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 breathing room. */
  gap: 6px;
  white-space: nowrap;
}

/* 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. */
.la-brand-icon {
  color: #EB6FBD;
}
body:has(.look-layout) .la-brand-icon {
  color: var(--wa-color-brand-fill-loud);
}

/* Header layout. Desktop: flex row, brand on left, actions on right
   (the primary-nav partial sits in the middle via natural flow).
   Mobile: 3-col grid with hamburger left, brand centered, actions
   right — matches the established mobile pattern users expect from
   stock app shells. The mobile rule lives under wa-page[view="mobile"]
   below. */
.la-shell-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wa-space-m);
  width: 100%;
}
.la-shell-header-actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Mobile-only hamburger. Hidden on desktop where the primary nav
   renders inline in the header instead. wa-page's auto-injected
   ::part(navigation-toggle) is also hidden because we render our
   own toggle inside the header slot — having two would double-up. */
.la-shell-nav-toggle { display: none; }
.la-shell::part(navigation-toggle) { display: none; }
wa-page[view="mobile"] .la-shell-nav-toggle { display: inline-flex; }

/* Mobile header layout: 3-col grid keeps the brand centered between
   the hamburger (left) and the right-cluster actions (right). The
   1fr/auto/1fr template gives equal-width gutters on both sides of
   the brand, which is what makes it read as "centered" regardless
   of how many actions sit on the right. */
wa-page[view="mobile"] .la-shell-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--wa-space-xs);
}
wa-page[view="mobile"] .la-shell-header > .la-shell-nav-toggle {
  justify-self: start;
}
wa-page[view="mobile"] .la-shell-header > .la-brand {
  justify-self: center;
}
wa-page[view="mobile"] .la-shell-header > .la-shell-header-actions {
  justify-self: end;
}

/* Narrow viewport: drop the "Look Awesome" wordmark, keep just the
   palette glyph. Threshold is 340px — only the very narrowest devices
   (iPhone SE 1st-gen at 320 logical px is the realistic floor) drop
   to the icon. iPhone SE 2nd-gen (375), iPhone 13 mini (375), and
   everything wider keeps the wordmark. */
@media (max-width: 340px) {
  .la-brand-text { display: none; }
}

/* Header action triggers (dark-mode toggle, account dropdown trigger,
   Sign In link). Anchors and buttons paint identically — reset native
   button chrome, baseline-align via inline-flex. Sits on the
   header-tinted surface, so neutral text reads against it cleanly. */
.la-shell-action {
  font-family: var(--wa-font-family-body);
  font-size: var(--wa-font-size-s);
  font-weight: 500;
  color: var(--wa-color-text-quiet);
  text-decoration: none;
  padding: 6px 10px;
  border-radius: 4px;
  background: transparent;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: color 0.1s, background 0.1s;
}
.la-shell-action:hover {
  color: var(--wa-color-text-loud);
  background: white;
}
.wa-dark .la-shell-action:hover {
  color: var(--wa-color-neutral-95);
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
}

/* Primary nav. Rendered twice in the layout (see shared/_primary_nav)
   — once inline in the header slot for desktop, once inside the
   navigation slot for mobile drawer. Quiet by default, loud on
   hover/active so the current page reads at a glance. No dark
   inversion — per `no-dark-sidebar` learning, Dave prefers subtle
   top nav, not contrast-flipped chrome. */
.la-nav {
  display: flex;
  align-items: center;
  gap: 4px;
}
.la-nav-link {
  font-family: var(--wa-font-family-body);
  font-size: var(--wa-font-size-s);
  font-weight: 500;
  color: var(--wa-color-text-quiet);
  text-decoration: none;
  padding: 6px 12px;
  border-radius: 4px;
  transition: color 0.1s, background 0.1s;
  display: inline-flex;
  align-items: center;
}
.la-nav-link:hover,
.la-nav-link.active {
  color: var(--wa-color-text-loud);
  /* Pure white pops against the off-white header (which uses
     --wa-color-neutral-95). Matches the prior .la-nav-link.active
     treatment exactly — one neutral shade off of the surrounding
     surface, on the brighter side in light mode and the lighter-
     gray side in dark mode. */
  background: white;
}
.wa-dark .la-nav-link:hover,
.wa-dark .la-nav-link.active {
  color: var(--wa-color-neutral-95);
  background: color-mix(in oklab, var(--wa-color-neutral-05), white 12%);
}

/* Drawer placement (mobile, inside wa-page's slide-in panel): stack
   vertically with larger tap targets. The wa-page[view="mobile"] host
   attribute scopes this to the drawer-active state — desktop view
   keeps the inline-row layout above. */
.la-nav--drawer {
  flex-direction: column;
  align-items: stretch;
  gap: var(--wa-space-2xs);
  padding: var(--wa-space-s);
}
.la-nav--drawer .la-nav-link {
  padding: var(--wa-space-s) var(--wa-space-m);
}

/* Visibility flip between the two nav copies. Desktop: header copy
   visible, drawer copy hidden in DOM but wa-page's hamburger toggle
   doesn't render either (because mobile-breakpoint isn't crossed).
   Mobile: header copy hidden, drawer copy visible inside the slide-in
   panel. wa-page reflects view="desktop|mobile" on its host based on
   viewport vs. mobile-breakpoint (default 768px). */
wa-page[view="mobile"] .la-nav--header { display: none; }
wa-page[view="desktop"] .la-nav--drawer { display: none; }

/* Main content slot — keep the same horizontal rhythm the old
   .la-main had (1200px / 24px gutter / 32px top-bottom). wa-page
   gives us a sticky header above; this is just the inner box. */
.la-shell-main {
  max-width: 1200px;
  margin: 0 auto;
  padding: 32px 24px;
  width: 100%;
  box-sizing: border-box;
}

/* ─── 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. */
.la-footer {
  /* wa-page's slotted-region default is `display: flex`, which collapses
     the inner .la-footer-container to its content width (~720px) and
     defeats its `max-width: 1200; margin: 0 auto` centering. The footer
     was originally a block container with the inner div doing the
     centering — restore that here. (Header + navigation slots stay
     flex; their own layout was designed for it.) */
  display: block;
  color: var(--wa-color-text-quiet);
  padding: var(--wa-space-2xl) 0;
  margin-top: var(--wa-space-3xl);
}
.la-footer-container {
  /* Mirror .la-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 .la-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);
}
.la-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);
}
.la-footer-brand-block {
  flex: 1 1 320px;
  min-width: 0;
}
.la-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;
}
.la-footer-tagline {
  max-width: 60ch;
  margin: 0;
  font-size: var(--wa-font-size-s);
  line-height: 1.5;
}
.la-footer-col {
  display: flex;
  flex-direction: column;
  gap: var(--wa-space-2xs);
  flex: 0 0 auto;
}
.la-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;
}
.la-footer-col a {
  color: var(--wa-color-text-normal);
  text-decoration: none;
  font-size: var(--wa-font-size-m);
}
.la-footer-col a:hover {
  text-decoration: underline;
}
.la-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);
}
.la-footer-legal a {
  color: var(--wa-color-text-quiet);
  text-decoration: none;
}
.la-footer-legal a:hover {
  text-decoration: underline;
}
.la-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;
}
/* The display: grid above wins over UA's [hidden] { display: none }.
   Re-assert hiding so look-edit's target swap (empty-state vs editor)
   actually hides the editor on /new when no buffer is seeded. */
.look-layout[hidden] {
  display: none;
}
/* /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%;
}

/* .look-display-settings is the two-switch tray below source images.
 * A subtle top divider separates it from the source-images row above;
 * the eyebrow + the wa-stack gap carry the rhythm to the switches.
 * wa-switch's default emits its own label + hint with the right
 * proportions, so the section relies on WA's component styling and
 * just owns the divider + heading positioning. */
.look-display-settings {
  border-top: 1px solid var(--wa-color-surface-border);
  padding-top: var(--wa-space-m);
}
.look-display-settings .eyebrow {
  margin: 0;
}
/* The .eyebrow utility centers via line-height; the wa-stack-s gap
 * (8px) sits below it, which combined with WA's switch slot padding
 * reads as a natural section header → first row. */
/* Action row (Publish / Discard / Export). Each button stretches to
 * share the row equally — gives the primary commit + its companions
 * full-width presence in the narrow col-1 stack. flex-basis: 0 plus
 * grow: 1 makes the share even regardless of label length. */
.look-edit-actions > wa-button {
  flex: 1 1 0;
}
/* 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 {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* No own padding — wa-card's --spacing handles the outer inset for
     both sizes, keeping swatches flush with the meta row + title
     below. */
}
.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 loud default).
   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-60 (a
   medium-dark gray reading quiet on the page surface); dark mode
   inverts to neutral-40 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.
   Hovering anywhere on the wa-card (it has two .card-link sleeves
   plus the meta row) paints both the swatch glyph and the title
   brand, so the CTA reads as one resolving entry point. */
wa-card:has(.card-block-create):hover .card-block-create,
wa-card: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 is the stretched-overlay <a> covering the whole wa-card.
   Empty <a> with aria-label provides the accessible name; the card's
   content sits above it via position: relative + z-index 1, but with
   pointer-events: none so clicks fall through to the link. The
   action buttons in .card-meta re-enable pointer-events: auto so
   their forms fire instead of navigating. */
.card-link {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  text-decoration: none;
}
/* Click-pass-through for cards that have the stretched-link overlay.
   Only fires when the link is present (i.e., linked: true) so /show,
   /edit, and the /new draft surface — which don't render a card-link —
   stay fully interactive on their own. */
.look-card:has(> a.card-link) :is(.card-colors, .card-meta, .card-name) {
  pointer-events: none;
}
.look-card:has(> a.card-link) :is(.card-meta-action, .card-meta-actions form) {
  pointer-events: auto;
}

/* 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;
  /* Promote the card to its own composite layer so the lift's
     translateY(-2px) doesn't re-rasterize descendant SVG icons at
     sub-pixel positions during the 120ms transition — without this,
     the meta-row icons appear to jitter horizontally as the card
     animates. */
  will-change: transform;
}
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 title block — line 2 of the card layout. No own padding —
   wa-card's --spacing handles outer inset (sides + bottom), and the
   .card-meta row above provides the top gap via its own bottom
   padding. position: relative + z-index keep the title above the
   stretched .card-link overlay so its color resolution isn't
   masked by the link. */
.card-name {
  position: relative;
  z-index: 1;
}

/* Card + its lineage 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);
}
/* Lineage caption below the large preview card on /show + /edit. */
.card-lineage {
  margin: 0;
  color: var(--wa-color-text-quiet);
}

/* Author notes rendered between the card and the lineage caption on
   /show. Reads as body copy (not a quiet caption) — these are the
   author's own words about the Look and deserve normal text weight.
   pre-line preserves user-typed line breaks; word-break: break-word
   keeps long URLs / unbroken strings from blowing out the rail width. */
.look-notes {
  margin: 0;
  color: var(--wa-color-text-normal);
  white-space: pre-line;
  word-break: break-word;
}

/* Edit-mode card-name: hosts the look-name form input where the
   title would live on /show. The input takes the full card width
   so its host edges line up with the swatch rows above — wa-card's
   --spacing already insets both, no extra horizontal padding here.
   The 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. */
.card-name-edit {
  padding: var(--wa-space-m) 0 0;
}
.card-name-input {
  width: 100%;
  font-family: var(--wa-font-family-heading);
}

/* Notes textarea on /edit + /new — sits as a sibling of the look-card
   container in the look-info stack. Full column width matches the
   card above and the source-images block below. */
.look-notes-input {
  width: 100%;
}

/* Color-row name input (/edit color rows). Sits below the role-config
   row inside the wa-stack; the slug-hint caption sits below the input.
   Full width inside the row's wa-stack. */
.color-name-input {
  width: 100%;
}

/* Slug suffix icon inside the color-name wa-input (slot="end"). Quiet
   by default; the warning variant flips to danger fill when the typed
   name parameterizes to a reserved role slug. State is driven by the
   look-edit controller via _renderSlugHint setting
   data-color-name-suffix-state on the wa-input host. The empty-state
   hide is done via CSS rather than the `hidden` attribute because
   FA's i2svg replaces the original <i> with an <svg> that doesn't
   carry the attribute through; targeting the slotted icon by host
   data-state survives that rewrite. */
.color-name-slug-icon {
  color: var(--wa-color-text-quiet);
  cursor: help;
}
.color-name-slug-icon.fa-triangle-exclamation {
  color: var(--wa-color-danger-fill-loud);
}
.color-name-input[data-color-name-suffix-state="empty"] .color-name-slug-icon {
  display: none;
}

/* Named Buttons — 2-column grid so each named-button is 50% of the
   rail width, regardless of count. Long names truncate with an
   ellipsis on ::part(label) so a runaway palette name doesn't blow
   out the column. */
.cs-named-buttons {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--wa-space-xs);
}
.cs-named-buttons wa-button {
  width: 100%;
  min-width: 0;
}
.cs-named-buttons wa-button::part(base) {
  width: 100%;
  min-width: 0;
}
.cs-named-buttons wa-button::part(label) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
  min-width: 0;
}

/* Named Badges — inline-wrap cluster, same spacing as .cs-badges. */
.cs-named-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wa-space-xs);
}

/* Account-dropdown menu items. wa-dropdown-item doesn't accept href,
   so we wrap each item's icon + label in a full-width link. This rule
   makes the link fill the item's content box so clicks anywhere on
   the row navigate, matching the user expectation that "the icon and
   the label both link." Color inherits so WA's hover/focus styles on
   the host still apply visually. */
.la-dropdown-link {
  display: flex;
  align-items: center;
  gap: var(--wa-space-s);
  width: 100%;
  color: inherit;
  text-decoration: none;
}
.la-dropdown-icon {
  /* Match WA's slot="icon" visual treatment (matching size + opacity
     of the leading icons elsewhere in the project). */
  flex-shrink: 0;
  width: 1em;
  text-align: center;
}

/* Persistent banner shown when an admin is impersonating another user
   ("Sign in as" via devise_masquerade). Sits at the top of the main
   content area, warning-colored to stand out + remind the admin they
   are NOT operating as themselves. The back-link kicks them out of
   impersonation in one click. */
.masquerade-banner {
  display: flex;
  align-items: center;
  gap: var(--wa-space-s);
  padding: var(--wa-space-s) var(--wa-space-m);
  margin-bottom: var(--wa-space-m);
  background: var(--wa-color-warning-fill-quiet);
  border: 1px solid var(--wa-color-warning-border-quiet);
  border-radius: var(--wa-border-radius-m);
  color: var(--wa-color-warning-on-quiet);
}
.masquerade-banner .masquerade-back-link {
  margin-left: auto;
  color: var(--wa-color-warning-on-quiet);
  text-decoration: underline;
  white-space: nowrap;
}
/* 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,
   color, overflow, line-height) + size-specific clamp behavior.
   Color is pinned explicitly so the stretched .card-link <a> below
   doesn't leak text-link (brand) through inheritance. */
.card-name-text {
  margin: 0;
  color: var(--wa-color-text-normal);
  line-height: 1.15;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-word;
  width: 100%;
}
/* Regular gallery card: one line + ellipsis. */
.look-card-regular .card-name-text {
  white-space: nowrap;
}
/* Large preview card (/show, /edit): two-line clamp + ellipsis. */
.look-card-large .card-name-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

/* ─── 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 .la-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 {
  /* Quiet ghost cue inside the input slot — one step lighter than the
     card-action icons because they sit ON the input field surface,
     where wa-input's own border + label already supply enough chrome.
     4 shades past text-quiet, toward the surface. */
  color: var(--wa-color-neutral-80);
}
.wa-dark .auth-input-icon {
  color: var(--wa-color-neutral-20);
}
/* 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 nav handling lives inside <wa-page>'s mobile-breakpoint
   (768px) — the drawer collapse + hamburger toggle are wa-page's job
   now. The old @media query that hid .la-nav-links here is gone with
   the .la-nav-* chrome it served. */
