/* Dark academia rework, 2026-04-09.
 * Typographic choices follow Matthew Butterick's Practical Typography:
 * serif body, generous leading, small caps in place of all-caps,
 * restrained emphasis, one accent color. Class names kept for JS stability. */

/* Charter (Bitstream license, see /fonts/Charter license.txt) — flavor
 * accent on the headline only. IBM Plex Sans (SIL OFL, Mike Abbink +
 * Bold Monday for IBM, 2017) — body copy and all UI chrome. Latin-1
 * subset only, since this app is English-only. */
@font-face {
    font-family: 'Charter';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/charter_italic.woff2') format('woff2');
}
@font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/IBMPlexSans-Regular-Latin1.woff2') format('woff2');
}
@font-face {
    font-family: 'IBM Plex Sans';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/IBMPlexSans-Italic-Latin1.woff2') format('woff2');
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    /* midnight ink, parchment, brass */
    --ink-0: #00010a;          /* deepest well */
    --ink-1: #02040f;          /* page ground */
    --ink-2: #070c1e;          /* raised card */
    --rule: #172038;           /* hairline border */
    --rule-strong: #26324f;    /* hover border */
    --parchment: #e7ddc7;      /* body text */
    --parchment-dim: #b6aa8e;  /* secondary */
    --muted: #7a7058;          /* tertiary / labels */
    --brass: #c9a44c;          /* accent */
    --brass-bright: #d8b25a;   /* accent hover */
    --brass-dim: #8e7434;      /* accent at rest */
    --crimson: #b03a3a;        /* record dot, errors */
    --ember: #d66a3a;          /* overtime warning */
}

html {
    background-color: var(--ink-1);
}

body {
    font-family: 'IBM Plex Sans', system-ui, -apple-system, "Segoe UI", sans-serif;
    font-size: 18px;
    line-height: 1.5;
    background-color: var(--ink-1);
    /* Background layers, topmost first:
     *   1. Fleuron dingbats (❦) at the diagonal cells of a 2×2 checker,
     *      so the marks trace a continuous diagonal drift across the page.
     *   2. Warm parchment-tinted fractal noise for paper grain.
     *   3-6. Four radial gradients pinned to the corners for a Telegram-
     *        style freeform-gradient approximation. Each contributes a
     *        subtle tint that bleeds toward the center, so the surface
     *        reads as dimensional rather than flat. Colors drawn from
     *        the existing palette: deep blue top-left, dim crimson
     *        top-right, warm bronze bottom-left, deepest ink bottom-right. */
    background-image:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><g font-family='Georgia,serif' font-size='30' fill='%23c9a44c' fill-opacity='0.13' text-anchor='middle'><text x='60' y='76'>&#10086;</text><text x='180' y='196'>&#10086;</text></g></svg>"),
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.85  0 0 0 0 0.78  0 0 0 0 0.58  0 0 0 0.09 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"),
        radial-gradient(ellipse 70% 55% at 0% 0%, rgba(32, 48, 96, 0.19), transparent 70%),
        radial-gradient(ellipse 60% 50% at 100% 0%, rgba(80, 22, 46, 0.14), transparent 65%),
        radial-gradient(ellipse 60% 50% at 0% 100%, rgba(62, 42, 14, 0.12), transparent 65%),
        radial-gradient(ellipse 80% 60% at 100% 100%, rgba(0, 0, 4, 0.28), transparent 70%);
    background-repeat: repeat, repeat, no-repeat, no-repeat, no-repeat, no-repeat;
    background-size: 240px 240px, 200px 200px, 100% 100%, 100% 100%, 100% 100%, 100% 100%;
    background-attachment: scroll, scroll, fixed, fixed, fixed, fixed;
    color: var(--parchment);
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    justify-content: center;
    align-items: center;
    overflow-x: hidden;
    padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    font-feature-settings: "kern", "liga";
}

button,
textarea,
input {
    font: inherit;
}

[hidden] {
    display: none !important;
}

.site-shell {
    width: 100%;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.app {
    width: 100%;
    max-width: 560px;
    padding: 3.5rem 2rem;
    text-align: center;
}

/* ----- surface frame ---------------------------------------- */

.surface {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 3rem;
    animation: reveal 0.28s ease-out forwards;
    transition: opacity 0.3s ease-out;
}

.surface.is-leaving {
    opacity: 0.25;
}

.brand-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
    padding-bottom: 0.5rem;
}

.headline {
    font-family: 'Charter', Georgia, Cambria, serif;
    font-size: 4.7rem;
    font-weight: 400;
    font-style: italic;
    line-height: 1;
    white-space: pre-line;
    /* Extra bottom padding so background-clip: text has room to fill
     * Charter italic's f descender, which extends below the baseline
     * further than the tight line-height would normally allow. */
    padding-bottom: 0.1em;
    /* Tile the gradient per line so multi-line headlines (e.g.
     * "honesty is\ncourage.") get independent dark→bright ramps
     * instead of one gradient stretched across the full element. */
    background-size: 100% 1em;
    background-repeat: repeat-y;
    /* True debossed effect: the glyph itself has a vertical gradient
     * fill running dark → base → bright. The top of every letterform
     * sits in the shadow of its own lip; the bottom catches light
     * bouncing off the far rim. Combined with the text-shadow halo
     * (dark above, warm below the glyph), the two layers sell the
     * "pressed into the midnight surface" illusion.
     *
     * Technique: transparent text color + gradient background clipped
     * to the glyph shape via `background-clip: text`. Supported
     * everywhere with the -webkit- prefix as a safety net. */
    /* Debossed: bright parchment-cream fill with a gentle curved-
     * surface gradient. Shadow is minimal — just enough to suggest
     * a lip above and a rim-light below without eating contrast. */
    color: #d8c8a0;
    background-image: linear-gradient(
        180deg,
        #9a7a50 0%,
        #c0a878 10%,
        #e0d0a8 55%,
        #f5edd5 78%,
        #fdf8ee 100%
    );
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    text-shadow:
        0 -1px 1px rgba(0, 0, 0, 0.2),
        0 1px 1px rgba(255, 248, 225, 0.35);
}

/* Prose fragments — italic, parchment-dim */
.rules,
.record-caption,
.inline-note,
.push-copy,
.compose-status {
    color: rgba(231, 221, 199, 0.42);
    font-size: 0.95rem;
    font-style: italic;
    line-height: 1.6;
    white-space: pre-line;
}

/* Label set — small caps, muted, restrained tracking */
.countdown-phase,
.result-label {
    color: var(--muted);
    font-size: 0.95rem;
    font-variant-caps: all-small-caps;
    letter-spacing: 0.08em;
    line-height: 1.4;
}

.compose-status {
    color: var(--crimson);
}

.surface-body {
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
}

/* ----- cards ------------------------------------------------- */

.compose-card,
.notice-card,
.content-card,
.link-card {
    display: flex;
    flex-direction: column;
    gap: 2rem;
    /* Shift the card 8px up from the natural flex-gap position so the
     * divider sits closer to the headline. */
    margin-top: -0.5rem;
}

/* Stylized horizontal divider: thin line flanked by black-feathered
 * arrows (U+27B8) pointing outward. Unicode only has the rightward
 * form, so the left end is CSS-flipped via scaleX(-1) to mirror it.
 * Both the line and the ornaments inherit a single color via
 * `currentColor`. */
.divider {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0 0.25rem;
    margin-bottom: 0.5rem;
    color: rgba(231, 221, 199, 0.28);
}

.divider-line {
    flex: 1;
    height: 5px;
    display: block;
    color: inherit;
}

.divider-end {
    font-size: 1.5rem;
    line-height: 1;
    flex-shrink: 0;
    transform: translateY(-1px);
}

.divider-end-left {
    transform: translateY(-1px) scaleX(-1);
}

.compose-card.is-overtime {
    animation: pulse 1.6s ease-in-out infinite;
}

/* ----- countdown (subtle timeago line) ---------------------- */

.countdown-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
}

/* Phase label ("calm", "overtime") is internal vocabulary; overtime
 * still animates the compose-card pulse, so the text label adds noise. */
.countdown-phase {
    display: none;
}

.countdown-time {
    font-size: 0.95rem;
    font-style: italic;
    color: rgba(231, 221, 199, 0.55);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
}

.compose-card.is-overtime .countdown-time {
    color: rgba(214, 106, 58, 0.72);
}

/* ----- record stack ----------------------------------------- */

.record-stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
}

/* Record button + "end here" sit side-by-side on rally states so the
 * user reads them as alternatives — record a reply or end the channel
 * with a single glance. On landing (no rally-end) it's just the record
 * button centered. */
.record-row {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 1.5rem;
}

.record-btn {
    --glow-intensity: 0;
    width: 86px;
    height: 86px;
    border-radius: 50%;
    border: 1px solid var(--brass-dim);
    background: radial-gradient(circle at 50% 35%, rgba(231, 221, 199, 0.04), rgba(0, 0, 0, 0.25) 75%);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Raised effect + voice-activity glow layer. The glow's size and
     * alpha both scale with --glow-intensity (0 → silent, 1 → loud),
     * and audio.js writes that value every animation frame while
     * recording. Silent → transparent glow → effectively invisible. */
    box-shadow:
        0 0 calc(var(--glow-intensity) * 28px) calc(var(--glow-intensity) * 12px) rgba(204, 51, 51, calc(var(--glow-intensity) * 0.65)),
        0 2px 4px rgba(0, 0, 0, 0.55),
        0 10px 22px rgba(0, 0, 0, 0.4),
        inset 0 1px 0 rgba(231, 221, 199, 0.09);
    transition: border-color 0.25s, background 0.25s, transform 0.1s, opacity 0.25s;
}

.record-btn:hover:not(:disabled) {
    border-color: var(--brass);
    box-shadow:
        0 0 calc(var(--glow-intensity) * 28px) calc(var(--glow-intensity) * 12px) rgba(204, 51, 51, calc(var(--glow-intensity) * 0.65)),
        0 3px 5px rgba(0, 0, 0, 0.55),
        0 14px 28px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(231, 221, 199, 0.14);
}

.record-btn:active:not(:disabled) {
    transform: translateY(1px);
    box-shadow:
        0 0 calc(var(--glow-intensity) * 28px) calc(var(--glow-intensity) * 12px) rgba(204, 51, 51, calc(var(--glow-intensity) * 0.65)),
        0 1px 2px rgba(0, 0, 0, 0.55),
        0 4px 10px rgba(0, 0, 0, 0.4),
        inset 0 1px 0 rgba(231, 221, 199, 0.06);
}

.record-btn:disabled,
.send-btn:disabled,
.ghost-btn:disabled {
    opacity: 0.42;
    cursor: not-allowed;
}

.record-btn .record-icon {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--crimson);
    transition: border-radius 0.25s, background 0.25s;
}

.record-btn.is-recording .record-icon {
    border-radius: 3px;
    background: var(--parchment);
}

.record-time {
    color: var(--crimson);
    font-size: 1.35rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.04em;
}

/* ----- form fields ------------------------------------------ */

.link-out {
    width: 100%;
    background: var(--ink-0);
    border: 1px solid var(--rule);
    border-radius: 2px;
    padding: 0.9rem 1rem;
    color: var(--brass);
    font-family: inherit;
    font-size: 0.98rem;
    line-height: 1.55;
    text-align: center;
    letter-spacing: 0.01em;
}

.link-out::placeholder {
    color: var(--muted);
    font-style: italic;
}

.link-out:focus {
    outline: none;
    border-color: var(--brass-dim);
}

.audio-preview {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 0.85rem;
}

.audio-preview audio {
    display: none;
}

.played-audio {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
}

.played-audio audio {
    display: none;
}

.play-btn {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--brass-dim);
    background: transparent;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--parchment);
    padding: 0;
    line-height: 1;
    transition: border-color 0.25s, background 0.25s;
}

.play-btn:hover {
    border-color: var(--brass);
    background: rgba(201, 164, 76, 0.06);
}

.play-btn .play-icon {
    display: inline-block;
    font-size: 0.95rem;
    /* Nudge the triangle so it feels optically centered — the glyph
     * has more weight on its left edge, so shift right by 1px. */
    transform: translateX(1px);
}

.play-btn.is-playing .play-icon {
    transform: none;
}

.message-text {
    color: var(--parchment);
    font-size: 1.15rem;
    font-style: italic;
    line-height: 1.55;
    max-width: 420px;
    margin: 0 auto;
    word-wrap: break-word;
    white-space: pre-wrap;
}

/* ----- buttons ---------------------------------------------- */

.send-btn,
.ghost-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 46px;
    padding: 0.75rem 1.75rem;
    border-radius: 2px;
    cursor: pointer;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 400;
    font-variant-caps: all-small-caps;
    letter-spacing: 0.08em;
    transition: opacity 0.2s, border-color 0.25s, background 0.25s, color 0.25s;
}

.send-btn {
    background: var(--brass);
    color: var(--ink-0);
    border: 1px solid var(--brass);
}

.send-btn:hover:not(:disabled) {
    background: var(--brass-bright);
    border-color: var(--brass-bright);
}

.ghost-btn {
    background: transparent;
    color: var(--parchment-dim);
    border: 1px solid var(--rule-strong);
}

.ghost-btn:hover:not(:disabled) {
    color: var(--parchment);
    border-color: var(--brass-dim);
}

.compose-actions {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
}

.push-actions {
    display: flex;
    gap: 0.75rem;
}

/* Direct button children of a notice card (e.g. the listen prompt)
 * don't want to span the whole width — center them and pull in. */
.notice-card > .send-btn,
.notice-card > .ghost-btn {
    align-self: center;
    width: 70%;
}

/* Listen button doubles as a 3 s progress bar during the countdown.
 * Idle: filled brass. Counting: brass drains to outline, fills back
 * over 3 s via ::before. Text swaps to "save for later" (= cancel). */
.listen-btn {
    position: relative;
    overflow: hidden;
    z-index: 0;
}

.listen-btn.is-counting,
.listen-btn.is-counting:hover {
    background: transparent;
    /* Mid-tone between brass and ink — readable on both the dark
     * unfilled region and the brass fill as it sweeps across. */
    color: #5a4a28;
    border-color: var(--brass);
}

.listen-btn.is-counting::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 0;
    background: var(--brass);
    z-index: -1;
    animation: btn-fill 3s linear forwards;
}

@keyframes btn-fill {
    to { width: 100%; }
}

/* Link-card actions stay intrinsic-width and center — share reads as
 * modest, since the real action (sealing) is done. Copy gets a wider
 * hit target since it's the primary thing the sender does next. */
.link-actions {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
}

.link-actions .send-btn {
    min-width: 8rem;
}

/* "end here" is the text-terminator alternative to recording a reply.
 * It lives inline with the record button (.record-row), de-emphasized
 * so it reads as the quiet second option, not a primary action. */
.rally-end .ghost-btn {
    border: none;
    padding: 0.5rem 1rem;
    color: var(--muted);
    font-size: 0.9rem;
    white-space: nowrap;
}

.rally-end .ghost-btn:hover:not(:disabled) {
    color: var(--parchment-dim);
    background: transparent;
}

.compose-actions .send-btn,
.compose-actions .ghost-btn {
    flex: none;
    width: 70%;
}

.push-actions .send-btn,
.push-actions .ghost-btn {
    flex: 1;
}

.compact {
    min-height: 40px;
    padding: 0.55rem 1rem;
    font-size: 0.9rem;
}

/* ----- push prompt ------------------------------------------ */

.push-prompt {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--rule);
}

/* ----- sent confirmation ------------------------------------ */

.sent-tick {
    font-size: 1.8rem;
    font-style: italic;
    color: var(--brass);
}

/* ----- empty / 404 state ------------------------------------ */

.surface.is-empty .headline {
    font-size: 2rem;
}

/* ----- animations ------------------------------------------- */

.fade-in {
    animation: reveal 0.28s ease-out forwards;
}

@keyframes reveal {
    from {
        opacity: 0.55;
    }
    to {
        opacity: 1;
    }
}

@keyframes pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(176, 58, 58, 0.25);
    }
    50% {
        box-shadow: 0 0 0 10px rgba(176, 58, 58, 0);
    }
}

/* ----- mobile ----------------------------------------------- */

@media (max-width: 480px) {
    .app {
        padding: 2.5rem 1.25rem;
    }

    .headline {
        font-size: 3.8rem;
    }

    /* Prevent iOS zoom on input focus */
    .link-out,
    input, textarea, select {
        font-size: 16px;
    }

    .compose-actions,
    .link-actions,
    .push-actions {
        flex-direction: column;
    }
}

@media (max-width: 360px) {
    .app {
        padding: 2rem 1rem;
    }

    .headline {
        font-size: 3.05rem;
    }
}

/* Landscape phones — don't waste vertical space */
@media (max-height: 480px) and (orientation: landscape) {
    body {
        align-items: flex-start;
    }

    .app {
        padding-top: 1.5rem;
        padding-bottom: 1.5rem;
    }

    .surface {
        gap: 2rem;
    }
}
