/* ==========================================================================
   Arman Maulana, portfolio
   Aesthetic family: dark studio / native-craft. Not an official design
   system, and not claiming to be one. Tailwind utilities carry the layout;
   this file owns the design tokens, the media well, and the motion layer.

   Token strategy: CSS custom properties holding raw RGB channels, consumed by
   Tailwind via rgb(var(--x) / <alpha-value>) in the config in layouts/app.

   Theme: light is defined on bare :root. Dark is redefined twice, once under
   prefers-color-scheme (guarded so an explicit light choice still wins) and
   once under [data-theme="dark"], so the toggle wins in both directions.

   SHAPE SYSTEM (one documented rule, applied everywhere):
     interactive  (buttons, pills, chips, inputs, icon buttons)  -> full round
     containers   (cards, panels, media wells, drawers)          -> 1rem
     screenshots  (inside a media well)                          -> 0.75rem
   ========================================================================== */

:root {
    /* Light */
    --bg: 251 250 249;
    --surface: 255 255 255;
    --surface-2: 243 242 240;
    --fg: 23 23 26;
    --muted: 82 82 91;
    --subtle: 113 113 122;
    --line: 9 9 11;
    --accent: 194 65 12;

    /* Fixed in both themes: every project screenshot in this portfolio is a
       dark-background mockup plate, so the well it sits on stays dark too. */
    --media: 20 20 23;

    --ring: 0 0 0 2px rgb(var(--bg)), 0 0 0 4px rgb(var(--accent));
    color-scheme: light;
}

:root:not([data-theme="light"]) {
    @media (prefers-color-scheme: dark) {
        --bg: 11 11 14;
        --surface: 19 19 23;
        --surface-2: 26 26 32;
        --fg: 244 244 245;
        --muted: 161 161 170;
        --subtle: 138 138 147;
        --line: 255 255 255;
        --accent: 255 122 69;
        color-scheme: dark;
    }
}

/* Fallback for browsers without CSS nesting support. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: 11 11 14;
        --surface: 19 19 23;
        --surface-2: 26 26 32;
        --fg: 244 244 245;
        --muted: 161 161 170;
        --subtle: 138 138 147;
        --line: 255 255 255;
        --accent: 255 122 69;
        color-scheme: dark;
    }
}

:root[data-theme="dark"] {
    --bg: 11 11 14;
    --surface: 19 19 23;
    --surface-2: 26 26 32;
    --fg: 244 244 245;
    --muted: 161 161 170;
    --subtle: 138 138 147;
    --line: 255 255 255;
    --accent: 255 122 69;
    color-scheme: dark;
}

/* ==========================================================================
   Base
   ========================================================================== */

html {
    scroll-behavior: smooth;
    /* Sticky nav is 72px; keep anchor targets clear of it. */
    scroll-padding-top: 6rem;
    /* Decorative bleed (the hero wash) must never widen the document. `clip`
       rather than `hidden` so the sticky nav keeps working. */
    overflow-x: clip;
}

@supports not (overflow-x: clip) {
    body { overflow-x: hidden; }
}

body {
    background-color: rgb(var(--bg));
    color: rgb(var(--fg));
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

::selection {
    background-color: rgb(var(--accent) / 0.24);
    color: rgb(var(--fg));
}

:focus-visible {
    outline: none;
    box-shadow: var(--ring);
    border-radius: 0.5rem;
}

/* Tabular, aligned numerals wherever a figure sits next to another figure. */
.nums {
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}

/* Small mono label. Used sparingly, never as a section eyebrow. */
.label {
    font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.6875rem;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgb(var(--subtle));
}

/* ==========================================================================
   Buttons. One shape rule: interactive elements are fully round.
   Primary is a high-contrast neutral rather than the accent, which keeps
   every CTA well past WCAG AA without tinting the accent per surface.
   ========================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    white-space: nowrap;
    border-radius: 9999px;
    font-weight: 500;
    font-size: 0.9375rem;
    line-height: 1;
    padding: 0.875rem 1.375rem;
    transition: background-color .2s cubic-bezier(.16, 1, .3, 1),
                border-color .2s cubic-bezier(.16, 1, .3, 1),
                color .2s cubic-bezier(.16, 1, .3, 1),
                transform .12s cubic-bezier(.16, 1, .3, 1);
}

.btn:active {
    transform: translateY(1px);
}

/* fg on bg inverted: 16:1 in light, 17:1 in dark. */
.btn-primary {
    background-color: rgb(var(--fg));
    color: rgb(var(--bg));
}

.btn-primary:hover {
    background-color: rgb(var(--fg) / 0.86);
}

.btn-ghost {
    border: 1px solid rgb(var(--line) / 0.16);
    color: rgb(var(--fg));
}

.btn-ghost:hover {
    background-color: rgb(var(--line) / 0.05);
    border-color: rgb(var(--line) / 0.28);
}

.btn-sm {
    padding: 0.625rem 1.125rem;
    font-size: 0.875rem;
}

/* ==========================================================================
   Surfaces
   ========================================================================== */

.panel {
    background-color: rgb(var(--surface));
    border: 1px solid rgb(var(--line) / 0.10);
    border-radius: 1rem;
}

.hairline {
    border-color: rgb(var(--line) / 0.10);
}

/* Text link inside prose. */
.link {
    color: rgb(var(--accent));
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-thickness: 1px;
    text-decoration-color: rgb(var(--accent) / 0.4);
    transition: text-decoration-color .2s ease;
}

.link:hover {
    text-decoration-color: rgb(var(--accent));
}

/* Chip. Interactive shape rule applies: fully round. */
.chip {
    display: inline-flex;
    align-items: center;
    border-radius: 9999px;
    border: 1px solid rgb(var(--line) / 0.12);
    background-color: rgb(var(--line) / 0.03);
    color: rgb(var(--muted));
    font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.75rem;
    line-height: 1;
    padding: 0.4375rem 0.75rem;
    white-space: nowrap;
}

/* ==========================================================================
   Media well
   Every screenshot in this portfolio is a mockup plate already sitting on a
   dark canvas, so the container stays dark in both themes. This is a frame
   around image content, not a section flipping theme.
   ========================================================================== */

.media-well {
    background-color: rgb(var(--media));
    border: 1px solid rgb(255 255 255 / 0.08);
    border-radius: 1rem;
    overflow: hidden;
    position: relative;
}

.media-well img {
    display: block;
    width: 100%;
    height: auto;
}

.media-well--pad {
    padding: clamp(0.75rem, 2.5vw, 2rem);
}

.media-well--pad img {
    border-radius: 0.75rem;
}

/* ==========================================================================
   Project card
   ========================================================================== */

.pcard {
    display: flex;
    flex-direction: column;
    border-radius: 1rem;
    border: 1px solid rgb(var(--line) / 0.10);
    background-color: rgb(var(--surface));
    overflow: hidden;
    transition: border-color .3s cubic-bezier(.16, 1, .3, 1),
                transform .3s cubic-bezier(.16, 1, .3, 1);
}

.pcard:hover {
    border-color: rgb(var(--line) / 0.22);
    transform: translateY(-3px);
}

.pcard__media {
    background-color: rgb(var(--media));
    overflow: hidden;
}

.pcard__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .5s cubic-bezier(.16, 1, .3, 1);
}

.pcard:hover .pcard__media img {
    transform: scale(1.035);
}

/* The row height comes from the ordinary cards, so the lead card's media box
   ends up squarer than the screenshot's own ratio. Contain rather than crop;
   the plate's dark ground matches the well, so the bands are invisible. */
@media (min-width: 1024px) {
    .pcard__media--lead img {
        object-fit: contain;
    }
}

.pcard__arrow {
    transition: transform .3s cubic-bezier(.16, 1, .3, 1),
                color .3s cubic-bezier(.16, 1, .3, 1);
}

.pcard:hover .pcard__arrow {
    transform: translate(2px, -2px);
    color: rgb(var(--accent));
}

/* ==========================================================================
   Availability indicator
   A real semantic state (open for freelance work), used exactly once.
   ========================================================================== */

.status-dot {
    position: relative;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 9999px;
    background-color: rgb(var(--accent));
    flex: none;
}

.status-dot::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-color: rgb(var(--accent));
    animation: statusPulse 2.4s cubic-bezier(.16, 1, .3, 1) infinite;
}

@keyframes statusPulse {
    0%   { opacity: .7; transform: scale(1); }
    70%  { opacity: 0;  transform: scale(2.6); }
    100% { opacity: 0;  transform: scale(2.6); }
}

/* ==========================================================================
   Motion layer (MOTION_INTENSITY 7)
   Entrance cascade communicates reading order in the hero; scroll reveals
   communicate section boundaries. Both collapse under reduced motion.
   Scroll state comes from IntersectionObserver, never a scroll listener.
   ========================================================================== */

.reveal {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity .7s cubic-bezier(.16, 1, .3, 1),
                transform .7s cubic-bezier(.16, 1, .3, 1);
}

.reveal.is-in {
    opacity: 1;
    transform: none;
}

/* Stagger children of a revealed group. */
.reveal[data-delay="1"] { transition-delay: .08s; }
.reveal[data-delay="2"] { transition-delay: .16s; }
.reveal[data-delay="3"] { transition-delay: .24s; }
.reveal[data-delay="4"] { transition-delay: .32s; }
.reveal[data-delay="5"] { transition-delay: .40s; }

/* Hero entrance, runs on load rather than on scroll. */
.enter {
    opacity: 0;
    transform: translateY(16px);
    animation: enterUp .8s cubic-bezier(.16, 1, .3, 1) forwards;
}

.enter[data-delay="1"] { animation-delay: .06s; }
.enter[data-delay="2"] { animation-delay: .14s; }
.enter[data-delay="3"] { animation-delay: .22s; }
.enter[data-delay="4"] { animation-delay: .32s; }

@keyframes enterUp {
    to { opacity: 1; transform: none; }
}

/* Hero media parallax. Driven by two custom properties updated on
   pointermove inside a rAF, so nothing touches layout. Fine pointers only. */
.tilt {
    transform: translate3d(calc(var(--px, 0) * 10px), calc(var(--py, 0) * 10px), 0);
    transition: transform .5s cubic-bezier(.16, 1, .3, 1);
    will-change: transform;
}

.tilt--far {
    transform: translate3d(calc(var(--px, 0) * -14px), calc(var(--py, 0) * -14px), 0);
}

/* Ambient accent wash behind the hero media. Fixed size, no repaint cost. */
.hero-glow {
    position: absolute;
    inset: -12%;
    z-index: -1;
    border-radius: 9999px;
    background: radial-gradient(closest-side, rgb(var(--accent) / 0.18), transparent 72%);
    pointer-events: none;
}

/* ==========================================================================
   Navigation
   ========================================================================== */

.nav-shell {
    background-color: rgb(var(--bg) / 0.72);
    backdrop-filter: blur(14px) saturate(160%);
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    border-bottom: 1px solid transparent;
    transition: border-color .3s ease, background-color .3s ease;
}

.nav-shell.is-stuck {
    background-color: rgb(var(--bg) / 0.88);
    border-bottom-color: rgb(var(--line) / 0.10);
}

@media (prefers-reduced-transparency: reduce) {
    .nav-shell,
    .nav-shell.is-stuck {
        background-color: rgb(var(--bg));
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }
}

.nav-link {
    position: relative;
    color: rgb(var(--muted));
    transition: color .2s ease;
}

.nav-link:hover {
    color: rgb(var(--fg));
}

/* Theme toggle glyph. Shows the mode the button switches TO, and follows the
   same three-state resolution as the tokens above. */
.theme-icon-dark  { display: none; }
.theme-icon-light { display: inline-flex; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-icon-dark  { display: inline-flex; }
    :root:not([data-theme="light"]) .theme-icon-light { display: none; }
}

:root[data-theme="dark"] .theme-icon-dark  { display: inline-flex; }
:root[data-theme="dark"] .theme-icon-light { display: none; }

/* ==========================================================================
   Case study gallery
   JS toggles .is-active on .gslide figures and .is-current on .gdot buttons.
   ========================================================================== */

.gslide {
    display: none;
}

.gslide.is-active {
    display: block;
    height: 100%;
    animation: galleryIn .45s cubic-bezier(.16, 1, .3, 1);
}

/* The track reserves the tallest slide's aspect ratio, so each slide fills
   that box instead of resizing it. Beats `.media-well img` on specificity. */
.gslide picture {
    display: block;
    height: 100%;
}

.media-well .gslide img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

@keyframes galleryIn {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

.gdot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 9999px;
    background-color: rgb(var(--line) / 0.24);
    transition: width .3s cubic-bezier(.16, 1, .3, 1), background-color .3s ease;
}

.gdot.is-current {
    width: 1.5rem;
    background-color: rgb(var(--accent));
}

.gbtn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 9999px;
    background-color: rgb(20 20 23 / 0.72);
    border: 1px solid rgb(255 255 255 / 0.14);
    color: rgb(250 250 250);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    transition: background-color .2s ease, border-color .2s ease, transform .12s ease;
}

.gbtn:hover {
    background-color: rgb(20 20 23 / 0.92);
    border-color: rgb(255 255 255 / 0.3);
}

.gbtn:active {
    transform: scale(.94);
}

/* ==========================================================================
   Prose inside case studies
   ========================================================================== */

.prose-case p {
    color: rgb(var(--muted));
    font-size: 1.0625rem;
    line-height: 1.75;
}

.prose-case p + p {
    margin-top: 1.25rem;
}

/* ==========================================================================
   Accessibility and motion preferences
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    .reveal {
        opacity: 1;
        transform: none;
        transition: none;
    }

    .enter {
        opacity: 1;
        transform: none;
        animation: none;
    }

    .tilt,
    .tilt--far {
        transform: none;
    }

    .status-dot::after {
        animation: none;
    }

    .pcard:hover {
        transform: none;
    }

    .pcard:hover .pcard__media img {
        transform: none;
    }

    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
}

/* Coarse pointers get no parallax; there is no pointer to track. */
@media (pointer: coarse) {
    .tilt,
    .tilt--far {
        transform: none;
    }
}
