/* ===================================
   CV Builder Pro - Component Styles
   Reusable UI Components & Utilities
   =================================== */

/* ===================================
   Autosave Indicator
   =================================== */
.autosave-indicator {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs) var(--spacing-sm);
    background: var(--bg-secondary);
    border-radius: var(--radius-lg);
    border: 1px solid var(--border-color);
    font-size: 0.8125rem;
    color: var(--text-secondary);
    transition: all var(--transition-base);
}

.autosave-indicator .autosave-icon {
    display: none;
    font-size: 0.875rem;
}

.autosave-indicator.saved .autosave-icon.saved {
    display: block;
    color: var(--success-color);
}

.autosave-indicator.saving .autosave-icon.saving {
    display: block;
    color: var(--primary-color);
}

.autosave-indicator.unsaved .autosave-icon.unsaved {
    display: block;
    color: var(--warning-color);
    animation: pulse-dot 1.5s ease-in-out infinite;
}

.autosave-indicator.saved {
    border-color: color-mix(in srgb, var(--ok) 30%, transparent);
    background: var(--ok-wash);
}

.autosave-indicator.saving {
    border-color: rgba(var(--pen-rgb), 0.3);
    background: rgba(var(--pen-rgb), 0.05);
}

.autosave-indicator.unsaved {
    border-color: color-mix(in srgb, var(--warn) 30%, transparent);
    background: var(--warn-wash);
}

.autosave-text {
    font-weight: 500;
}

/* Ctrl+S used to raise a toast saying "Progress saved" beside a header
   already reading "Saved". The state is the answer; this is only what
   makes you notice it. Opacity, so it composites. */
.autosave-indicator.is-confirming {
    animation: state-confirm 600ms var(--ease-out);
}

@keyframes state-confirm {
    0%, 100% { opacity: 1; }
    30%      { opacity: 0.35; }
}

@keyframes pulse-dot {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

/* Smooth step transition container */
#formContent {
    position: relative;
    transition: opacity var(--transition-base), transform var(--transition-base);
}

#formContent.transitioning-out {
    opacity: 0;
    transform: translateX(-20px);
}

#formContent.transitioning-in {
    opacity: 0;
    transform: translateX(20px);
}

#formContent.visible {
    opacity: 1;
    transform: translateX(0);
}

/* ===================================
   Mobile Progress Indicator
   =================================== */
/* Three things were saying "step 3 of 10" at the same time on a phone: the
   filled bar above, a bordered panel, and ten dots inside it. The bar
   already draws the proportion and the line already says the numbers, so
   the panel and the dots are gone and the line sits under the bar as a
   caption.

   The dots also could not be tapped and were never meant to be, which is
   ten decorative circles in a row on the narrowest screen we have. */
.mobile-progress {
    display: none;
    margin-bottom: var(--spacing-md);
}

.mobile-progress-text {
    font-size: var(--font-size-sm);
    color: var(--ink-2);
}

.mobile-progress-text strong {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--font-weight-medium);
    color: var(--ink-2);
}

.mobile-progress-dots {
    display: none;
}

@media (max-width: 768px) {
    .mobile-progress {
        display: block;
    }
}

/* The theme toggle used to be a 44px button of its own in the header row.
   It is a preference you set once, so it is a line in the menu now, and its
   two states live beside .header-menu-item in css/style.css. */

/* ===================================
   Enhanced Cards
   =================================== */
.card {
    background: var(--bg-primary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-xl);
    padding: var(--spacing-lg);
    transition: all var(--transition-base);
}

.card:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}

/* .badge and its five variants were here and nothing in the app rendered
   through them (D-55 Q4). They carried text-transform: uppercase, so
   stringsRenderedInAllCaps read zero not because the rule was respected but
   because the rule was dead: the first person to reach for .badge would have
   broken the count and had no idea why. Deleted rather than left as a trap.

   .divider-with-text was the other half of R1's §4 note and went the same
   way in R3 (D-57 Q4). Between them they were the whole reason
   stringsRenderedInAllCaps read zero, so that count now means the rule is
   kept rather than that nothing renders through it.

   The pair left a hole behind, worth writing down: the OR on the old start
   screen was capitals typed into the markup, which text-transform never
   touched, so neither shouting counter saw it. wordsShoutedInSource wants
   two capitals rather than three from R3 on. */

/* ===================================
   Tooltips
   =================================== */
[data-tooltip] {
    position: relative;
}

[data-tooltip]::before,
[data-tooltip]::after {
    position: absolute;
    opacity: 0;
    visibility: hidden;
    transition: all var(--transition-fast);
    z-index: var(--z-tooltip);
    pointer-events: none;
}

[data-tooltip]::before {
    content: attr(data-tooltip);
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%) translateY(-4px);
    padding: var(--spacing-xs) var(--spacing-sm);
    background: var(--text-primary);
    color: var(--bg-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    border-radius: var(--radius-sm);
    white-space: nowrap;
    box-shadow: var(--shadow-lg);
}

[data-tooltip]::after {
    content: '';
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: var(--text-primary);
}

[data-tooltip]:hover::before,
[data-tooltip]:hover::after {
    opacity: 1;
    visibility: visible;
}

[data-tooltip]:hover::before {
    transform: translateX(-50%) translateY(-8px);
}

@keyframes skeleton-loading {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* ===================================
   Dividers
   =================================== */
.divider {
    height: 1px;
    background: var(--border-color);
    margin: var(--spacing-lg) 0;
}

/* ===================================
   Focus Visible Styles (Accessibility)
   =================================== */
:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

/* Remove default focus outline when not using keyboard */
:focus:not(:focus-visible) {
    outline: none;
}

/* ===================================
   Skip Link (Accessibility)
   ===================================
   Must stay identical to the block in css/landing.css: the two sheets are
   never loaded together, so drift between them is invisible. D-80 Q1, and
   walk_skip_link in tests/a11y-keyboard.py, which guards both pages and
   carries the long version. */
.skip-link {
    position: absolute;
    top: -100%;
    left: var(--spacing-md);
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--primary-color);
    color: var(--on-pen);
    border-radius: var(--radius-md);
    z-index: var(--z-tooltip);
    transition: top var(--transition-fast);
}

.skip-link:focus {
    top: var(--spacing-md);
}

/* ===================================
   Screen Reader Only
   =================================== */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

/* ===================================
   Glass Effect
   =================================== */
.glass {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

/* ===================================
   Toast

   Was four hand-written gradients in js/utils.js, painted onto the element
   with style.cssText, and white text on top of them. Two problems beyond
   the colours not coming from the tokens: white on the amber was 3.7:1,
   which is not readable text at 14px, and every one of the four was a
   gradient between two shades of the same hue, which the direction rules
   out.

   A wash with an ink message and a rule down the edge carrying the status
   colour says the same thing, reads at about 15:1 in both themes, and is
   what the rest of the app already looks like.
   =================================== */

.toast {
    position: fixed;
    right: var(--spacing-xl);
    bottom: var(--spacing-xl);
    z-index: var(--z-toast);
    max-width: min(30rem, calc(100vw - var(--spacing-xl) * 2));
    padding: var(--spacing-md) var(--spacing-lg);
    border: 1px solid var(--rule);
    border-left: 3px solid var(--ink-soft);
    border-radius: var(--radius-md);
    background: var(--paper-raised);
    color: var(--ink);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
    box-shadow: var(--shadow-lg);
    animation: toast-in var(--transition-slow);
}

.toast-success { border-left-color: var(--ok);   background: var(--ok-wash); }
.toast-error   { border-left-color: var(--stop); background: var(--stop-wash); }
.toast-warning { border-left-color: var(--warn); background: var(--warn-wash); }
.toast-info    { border-left-color: var(--pen);  background: var(--pen-wash); }

.toast.is-leaving { animation: toast-out var(--transition-slow) forwards; }

/* The import summary the extraction toast carries. Was a style attribute
   on the list, one on every row, and one on the closing line. */
.toast-items {
    list-style: none;
    margin: var(--spacing-xs) 0;
    padding: 0;
    text-align: left;
}

.toast-items li {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-2xs) 0;
}

.toast-items i { color: var(--ok); }

.toast-note { color: var(--ink-soft); }

/* Transform and opacity only, and they used to be injected into a <style>
   element at load by js/utils.js. That element is governed by style-src,
   so it would have stopped working the moment 'unsafe-inline' came out of
   the policy, and the toast would have appeared without sliding and never
   left. Here it is in a stylesheet, which needs no permission at all. */
@keyframes toast-in {
    from { transform: translateX(100%); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}

@keyframes toast-out {
    from { transform: translateX(0);    opacity: 1; }
    to   { transform: translateX(100%); opacity: 0; }
}

/* ─── where a message goes on a phone ────────────────────────────────
   Bottom right, and on a phone that was 258 to 297px below the glass,
   because the header stretched the layout viewport and this is pinned to
   it (D-54). Every message this app has ever shown a phone user, including
   every error, arrived there.

   Making the page fit puts it back on screen, but bottom right is still
   the wrong corner below the split-view width, for two reasons that have
   nothing to do with that bug: the preview pill is already there, and this
   is a form app, so the bottom of the screen is where the keyboard is. A
   fixed element at the bottom of an iOS page with the keyboard up is
   behind the keyboard.

   So below 1180 it is a strip under the header instead: the same width as
   the content, the full message readable without a line break, and clear
   of both the pill and the keyboard. tests/mobile-viewport.py checks that
   it lands on the glass and that it does not overlap the pill. */
@media (max-width: 1179px) {
    .toast {
        top: calc(var(--header-height) + var(--spacing-xs));
        inset-inline: var(--spacing-sm);
        bottom: auto;
        max-width: none;
        animation-name: toast-drop;
    }

    .toast.is-leaving {
        animation-name: toast-lift;
    }
}

@keyframes toast-drop {
    from { transform: translateY(-120%); opacity: 0; }
    to   { transform: translateY(0);     opacity: 1; }
}

@keyframes toast-lift {
    from { transform: translateY(0);     opacity: 1; }
    to   { transform: translateY(-120%); opacity: 0; }
}

/* ===================================
   AI panels

   The three AI modals wrote their own padding, borders and fills into
   style attributes, and the headline list changed its hover colour from
   JavaScript with four literals: #f0f9ff, #3b82f6, white, #ddd. A hover
   state is what CSS is for, and those were the last colours in the app
   outside the tokens.
   =================================== */

.ai-suggestion-item {
    padding: var(--spacing-md);
    margin: var(--spacing-sm) 0;
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    background: var(--paper-raised);
    cursor: pointer;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}

.ai-suggestion-item:hover,
.ai-suggestion-item:focus-visible {
    background: var(--pen-wash);
    border-color: var(--pen);
}

.ai-panel {
    padding: var(--spacing-md);
    margin-bottom: var(--spacing-lg);
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface);
}

.ai-panel-label {
    margin-bottom: var(--spacing-xs);
    font-size: var(--font-size-sm);
    color: var(--ink-soft);
}

.ai-panel.is-suggested {
    border-color: var(--pen);
    background: var(--pen-wash);
}

.ai-panel.is-checked {
    border-color: var(--ok);
    background: var(--ok-wash);
}

.ai-panel pre {
    margin: 0;
    font-family: inherit;
    white-space: pre-wrap;
}

.ai-actions {
    display: flex;
    gap: var(--spacing-sm);
    justify-content: flex-end;
    margin-top: var(--spacing-lg);
}

/* The keyword analyser's closing panel. Its fill was written inline as
   linear-gradient(135deg, rgba(102,126,234,.05), rgba(118,75,162,.05)):
   indigo into violet, which is the exact gradient docs/08-design-direction
   .md opens by naming as the signature of a generated design. It was still
   running inside a modal two phases after the identity landed, because
   nothing looked in there. */
.kw-callout {
    margin-top: var(--spacing-lg);
    padding: var(--spacing-md);
    border-left: 4px solid var(--pen);
    border-radius: var(--radius-md);
    background: var(--pen-wash);
}

.kw-callout h4 {
    margin-bottom: var(--spacing-xs);
}
