/* ============================================================================
   ACCOUNT SETTINGS (/app/?view=settings)

   The eight-panel account surface, lifted out of myworkshopr/settings.php and
   rescoped from that page's body to `.app-settings`, so none of it reaches
   another view in the shell. Every selector below starts at that class for
   exactly that reason: the old sheet claimed `.form-group`, `.panel-card`,
   `.switch` and `body` itself.

   One local layer, the four --settings-* accents. The old page recoloured
   itself from a `$fromPlanner` branch — planner blue on one route, mainsite
   red on the other — which Red Unification retired. There is one accent now,
   named once here so the nav pill, the focus ring and the switches cannot
   drift apart again.

   Nothing here keys off a viewport unit or a fixed pixel offset. This view
   scrolls inside the shell's own region rather than the window, so anything
   measured against the viewport lands in the wrong place — the sticky nav's
   old `top: 100px` was an offset for a page header that does not exist inside
   this scroll region.
   ============================================================================ */

/* No local cap: the view sits in the six-column main of the page grid
   (app-shell.css, .app-page), which owns the width. */
.app-settings {
    --settings-accent: var(--phase-learn);
    --settings-accent-dark: var(--phase-learn-dark);
    --settings-accent-soft: var(--phase-learn-very-light);
    --settings-accent-ring: var(--phase-learn-light);
}

/* Nav and panel, inside the 928 main of the page grid: 240 for the nav, 40 of
   gap, 648 for the panel. The account summary that used to be a third column
   here is the shell rail's lead now (settings.php hands it to appShellRail()),
   so the view has the same aside as every other, at the rail's 296. At 648 the
   panel's two-column form rows still land two fields of about 300 each. */
.app-settings__layout {
    display: grid;
    grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
    gap: var(--space-10);
    align-items: start;
}

/* ── section nav ─────────────────────────────────────────────────────────── */

.app-settings__nav {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    position: sticky;
    top: var(--space-6);
}

.app-settings__nav-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.app-settings__nav-label {
    font-size: var(--text-caption);
    font-weight: var(--font-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    /* The faint ink is documented decorative-only; a group label names the
       rows under it, so it takes the accessible variant. */
    color: var(--color-ink-faint-accessible);
    margin-bottom: var(--space-2);
    padding-left: var(--space-3);
}

.app-settings__nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    padding: var(--space-3) var(--space-4);
    border: none;
    background: transparent;
    border-radius: var(--radius-xl);
    font-family: var(--font-body);
    font-size: var(--text-small);
    font-weight: var(--font-medium);
    color: var(--color-ink-secondary);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.app-settings__nav-item .material-symbols-outlined {
    font-size: var(--icon-md);
    color: var(--color-ink-muted);
}

.app-settings__nav-item:hover {
    background: var(--gray-100);
    color: var(--color-ink);
}

.app-settings__nav-item.is-active {
    background: var(--settings-accent);
    color: var(--text-inverse);
    box-shadow: var(--shadow-sm);
}

.app-settings__nav-item.is-active .material-symbols-outlined {
    color: var(--text-inverse);
}

.app-settings__nav-item--danger { color: var(--color-error-dark); }
.app-settings__nav-item--danger:hover { background: var(--color-error-very-light); color: var(--color-error-dark); }

/* ── panels ──────────────────────────────────────────────────────────────── */

.app-settings__panel { display: none; }

.app-settings__panel.is-active {
    display: block;
    animation: app-settings-fade var(--transition-base);
}

@keyframes app-settings-fade {
    from { opacity: 0; transform: translateY(5px); }
    to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .app-settings__panel.is-active { animation: none; }
}

/* The card family's own tokens, not a private copy. This drew --radius-2xl
   (16px) where every other card in the frame draws --card-radius (12px), which
   made the Settings panel the one surface with different corners; the fill,
   border and shadow said the same thing as the tokens by coincidence rather
   than by reference. The inset stays roomy, because 20px against an 840px panel
   is barely an inset, but it is --card-padding-roomy now rather than a raw step
   off the spacing scale. */
.app-settings__card {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--card-radius);
    padding: var(--card-padding-roomy);
    box-shadow: var(--card-shadow);
}

.app-settings__card + .app-settings__card { margin-top: var(--space-6); }

.app-settings__card--danger {
    background: var(--color-error-very-light);
    border-color: var(--color-error-light);
}

.app-settings__card-head { margin-bottom: var(--space-8); }

.app-settings__title {
    font-family: var(--font-heading);
    font-size: var(--text-h2);
    font-weight: var(--font-bold);
    color: var(--color-ink);
    margin: 0 0 var(--space-2);
}

.app-settings__card--danger .app-settings__title { color: var(--color-error-dark); }

.app-settings__subtitle {
    font-size: var(--text-body);
    color: var(--color-ink-muted);
    margin: 0;
}

/* ── profile photo ───────────────────────────────────────────────────────────
   Not ws_avatar: that component renders either an image or initials, and this
   one is swapped between the two at runtime by the upload handler, so both
   nodes have to exist in the DOM at once. */

.app-settings__avatar-row {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    margin-bottom: var(--space-8);
    padding-bottom: var(--space-8);
    border-bottom: 1px solid var(--gray-100);
}

.app-settings__avatar {
    width: var(--space-16);
    height: var(--space-16);
    border-radius: var(--radius-full);
    background: var(--bg-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    flex-shrink: 0;
    transition: opacity var(--transition-fast);
}

/* Which of the two shows is the wrapper's state, not an inline style written
   onto each node by the upload handler. */
.app-settings__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: none;
}

.app-settings__avatar.has-photo img { display: block; }
.app-settings__avatar.has-photo .app-settings__avatar-initial { display: none; }

/* .ws-btn is display:inline-flex, which outranks the browser's [hidden] rule. */
.app-settings__avatar-remove[hidden] { display: none; }

.app-settings__avatar-initial {
    font-family: var(--font-heading);
    font-size: var(--text-h2);
    font-weight: var(--font-bold);
    color: var(--settings-accent);
}

.app-settings__avatar-copy h3 {
    font-size: var(--text-h4);
    margin: 0 0 var(--space-1);
    color: var(--color-ink);
}

.app-settings__avatar-copy p {
    font-size: var(--text-small);
    color: var(--color-ink-muted);
    margin: 0 0 var(--space-4);
}

.app-settings__button-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* ── forms ───────────────────────────────────────────────────────────────────
   Fields are ws_input / ws_select, so the control styling comes from
   components.css. What is left here is the grid they sit in and the accent on
   focus, which the component draws from --input-* tokens platform-wide and
   which this view keys to its own accent. */

.app-settings__grid-2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-5);
}

.app-settings__form .ws-field { margin-bottom: var(--space-5); }
.app-settings__grid-2 .ws-field { margin-bottom: 0; }

.app-settings__form .ws-input:focus,
.app-settings__form .ws-select:focus {
    border-color: var(--settings-accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--settings-accent-ring);
}

.app-settings__actions {
    display: flex;
    justify-content: flex-start;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

/* ── toggle rows ─────────────────────────────────────────────────────────── */

.app-settings__list {
    display: flex;
    flex-direction: column;
}

.app-settings__row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-6);
    padding: var(--space-5) 0;
    border-bottom: 1px solid var(--gray-100);
}

.app-settings__row:last-child { border-bottom: none; }

.app-settings__row-copy h3 {
    font-size: var(--text-body);
    font-weight: var(--font-semibold);
    color: var(--color-ink);
    margin: 0 0 var(--space-1);
}

.app-settings__row-copy p {
    font-size: var(--text-small);
    color: var(--color-ink-muted);
    margin: 0;
}

.app-settings__row .ws-toggle__input:checked + .ws-toggle__slider {
    background: var(--settings-accent);
}

/* ── subscription ────────────────────────────────────────────────────────── */

.app-settings__plan {
    background: linear-gradient(135deg, var(--color-ink) 0%, var(--color-ink-secondary) 100%);
    color: var(--text-inverse);
    border-radius: var(--radius-2xl);
    padding: var(--space-8);
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-6);
    margin-bottom: var(--space-8);
    box-shadow: var(--shadow-md);
}

/* The one state that is not a status readout but a problem to fix, so it wears
   the error surface rather than the ink one. */
.app-settings__plan--overdue {
    background: linear-gradient(135deg, var(--color-error-darker) 0%, var(--color-error-strong) 100%);
}
/* The plan card is a two-column flex, so a branch with two controls needs its
   own row rather than a second column. */
.app-settings__plan-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}
.app-settings__error {
    margin: 0 0 var(--space-6);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-error-light);
    border-radius: var(--radius-lg);
    background: var(--color-error-very-light);
    color: var(--color-error-dark);
    font-size: var(--text-small);
}

/* The colour is stated on each child rather than left to inherit from the card:
   the global h3 rule sets its own ink, which wins over inheritance and paints
   the headline dark-on-dark. */
.app-settings__plan h3 {
    margin: 0 0 var(--space-2);
    font-family: var(--font-heading);
    font-size: var(--text-h3);
    color: var(--text-inverse);
}

.app-settings__plan p {
    margin: 0;
    font-size: var(--text-small);
    max-width: 46ch;
    color: var(--text-inverse);
    opacity: 0.85;
}

/* The card's own action, on the card's own ground: the platform's secondary
   button is red on white, which on this surface is two dark colours arguing. */
.app-settings__plan .ws-btn--secondary {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.5);
    color: var(--text-inverse);
}

.app-settings__plan .ws-btn--secondary:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: var(--text-inverse);
    color: var(--text-inverse);
}

/* ws_badge, recoloured for the dark card it sits on: the default badge surface
   is a light tint and disappears against this gradient. */
.app-settings__plan .ws-badge {
    background: rgba(255, 255, 255, 0.15);
    color: var(--text-inverse);
    margin-bottom: var(--space-4);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

.app-settings__billing h3 {
    font-family: var(--font-heading);
    font-size: var(--text-h4);
    color: var(--color-ink);
    margin: 0 0 var(--space-3);
}

.app-settings__billing p {
    font-size: var(--text-small);
    color: var(--color-ink-muted);
    margin: 0;
}

/* The portal is a POST — Stripe needs the session created server-side — so the
   button that opens it carries a form rather than an href. */
.app-settings__inline-form { display: inline; }

/* The cancel dialog's prose: ws_modal leaves body copy unstyled, so without
   this it inherited a 9.8px black paragraph with no spacing. It sits on the
   same tokens as the cards around it. */
.app-settings__panel .ws-modal__body > * + * {
    margin-top: var(--space-4);
}

.app-settings__panel .ws-modal__body > p {
    margin-bottom: 0;
    font-size: var(--text-body);
    line-height: var(--leading-relaxed);
    color: var(--color-ink);
}

.app-settings__panel .ws-modal__body > p + p {
    margin-top: var(--space-2);
    color: var(--color-ink-muted);
}

.app-settings__panel .ws-modal__body a {
    color: var(--phase-learn-dark);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.app-settings__billing-form { margin-top: var(--space-3); }

/* ── connected accounts ──────────────────────────────────────────────────── */

.app-settings__connect {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4);
    background: var(--bg-muted);
    border-radius: var(--radius-xl);
}

.app-settings__connect-name {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--text-small);
    font-weight: var(--font-semibold);
    color: var(--color-ink);
}

/* ── appearance ──────────────────────────────────────────────────────────────
   ws_radio_card_group draws the cards, their grid and the selected state (it
   paints from `input:checked`, which is why nothing here restates it — an
   override on the label box, rather than on the card's visual, leaks its
   background out from under the border). All this sets is where the pair of
   cards should stop widening. */

.app-settings__appearance {
    max-width: 320px;
    margin-bottom: var(--space-6);
}

/* ── danger zone ─────────────────────────────────────────────────────────── */

.app-settings__danger-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-6);
    padding: var(--space-5) 0;
    border-bottom: 1px solid var(--color-error-light);
}

.app-settings__danger-row:last-child { border-bottom: none; }

.app-settings__danger-row h3 {
    font-size: var(--text-body);
    font-weight: var(--font-semibold);
    color: var(--color-ink);
    margin: 0 0 var(--space-1);
}

.app-settings__danger-row p {
    font-size: var(--text-small);
    color: var(--color-ink-muted);
    margin: 0;
}

/* ── narrow ──────────────────────────────────────────────────────────────────
   Matched to the shell's own mobile switch (app-shell.css, 900px), so the
   settings nav goes horizontal at the same width the rail becomes a bottom
   bar rather than at some second breakpoint of its own. */

@media (max-width: 900px) {
    .app-settings__layout { grid-template-columns: minmax(0, 1fr); }

    .app-settings__nav {
        position: static;
        flex-direction: row;
        gap: var(--space-2);
        overflow-x: auto;
        padding-bottom: var(--space-4);
    }

    .app-settings__nav-group { flex-direction: row; }
    .app-settings__nav-label { display: none; }
    .app-settings__nav-item { width: auto; white-space: nowrap; }

    .app-settings__grid-2 { grid-template-columns: minmax(0, 1fr); }
    .app-settings__grid-2 .ws-field { margin-bottom: var(--space-5); }

    .app-settings__card { padding: var(--card-padding); }

    .app-settings__plan,
    .app-settings__row,
    .app-settings__danger-row {
        flex-direction: column;
        align-items: flex-start;
    }
}


/* ── the account aside ────────────────────────────────────────────────────────
   Rendered inside the shell rail (.app-rail), which is the sticky element, so
   the aside itself no longer sticks: on a long panel (Notifications, Workshop
   Defaults) the whole rail stays beside the form.
   ---------------------------------------------------------------------------- */

.app-settings__aside {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-width: 0;
}

.app-settings__aside-card {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--card-radius);
    padding: var(--card-padding);
    box-shadow: var(--card-shadow);
}

.app-settings__aside-who {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.app-settings__aside-id { min-width: 0; }

.app-settings__aside-name {
    margin: 0;
    font-family: var(--font-heading);
    font-weight: var(--font-semibold);
    font-size: var(--text-h4);
    line-height: 1.3;
    color: var(--color-ink);
}

/* An address is one unbroken string, so it gets the ellipsis rather than a
   wrap that would push the card taller than the name it belongs to. */
.app-settings__aside-email {
    margin: 0;
    font-size: var(--text-caption);
    color: var(--color-ink-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-settings__aside-facts {
    margin: var(--space-4) 0 0;
    padding-top: var(--space-3);
    border-top: 1px solid var(--card-border);
    display: flex;
    flex-direction: column;
    gap: var(--space-1-5);
}

.app-settings__aside-fact {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.app-settings__aside-fact dt {
    font-size: var(--text-caption);
    color: var(--color-ink-muted);
}

.app-settings__aside-fact dd {
    margin: 0;
    font-size: var(--text-caption);
    color: var(--color-ink-secondary);
}

.app-settings__aside-title {
    margin: 0 0 var(--space-2);
    font-family: var(--font-heading);
    font-weight: var(--font-semibold);
    font-size: var(--text-small);
    color: var(--color-ink);
}

.app-settings__aside-note {
    margin: var(--space-2) 0 0;
    font-size: var(--text-caption);
    line-height: 1.55;
    color: var(--color-ink-muted);
}

.app-settings__aside-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    margin-top: var(--space-3);
    font-size: var(--text-caption);
    font-weight: var(--font-semibold);
    color: var(--settings-accent-dark);
    text-decoration: none;
}

.app-settings__aside-link .material-symbols-outlined {
    font-size: var(--text-small);
    transition: transform var(--duration-fast) var(--ease-out);
}

.app-settings__aside-link:hover .material-symbols-outlined { transform: translateX(2px); }

.app-settings__aside-link:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
    .app-settings__aside-link .material-symbols-outlined { transition: none; }
    .app-settings__aside-link:hover .material-symbols-outlined { transform: none; }
}
