From 7cf020c7bd8243bb0ede1482075c1cdfe5927c33 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Tue, 19 May 2026 14:55:10 +0200 Subject: [PATCH] Refactor CSS architecture per css-methodology-spec.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split CSS into named layers: reset → colors → typography → base → components → utilities. Each component has one unique root class in its own file. No cross-component overrides. New files: - reset.css (modern-normalize base — matches project's prior reset) - colors.css (all colour variables) - typography.css (font faces, size/space scale, font-family vars) - base.css (≤ 5 site-wide rules: layout, headings) - utilities.css (sr-only, skip-link, reduced-motion) - style.css (root @import file loading all layers) - components/{links,focus,forms,tables,dialog,details,media, buttons,badges,toasts,pagination,header,search}.css Existing files: - variables.css → backward-compat wrapper (imports colors + typography) - common.css → backward-compat wrapper (imports style.css) - Page files (admin, public, form, tfe, apropos, repertoire, system, file-access) → removed redundant @import url(./variables.css) - head.php → loads style.css instead of modern-normalize + common.css - partage pages → load style.css Fixes vs initial refactoring: - reset.css: use modern-normalize base (not Tailwind Preflight) to avoid border/list/heading regressions from aggressive defaults - components/search.css: restore !important flags on input styles (needed to override forms.css base input selectors) - acces.php: add toast feedback on password copy button Cleaned up duplicate status-badge/toast definitions from admin.css (now live in components/badges.css and components/toast.css). --- TODO.md | 16 + app/public/assets/css/admin.css | 118 +-- app/public/assets/css/apropos.css | 3 +- app/public/assets/css/base.css | 44 + app/public/assets/css/colors.css | 83 ++ app/public/assets/css/common.css | 761 +----------------- app/public/assets/css/components/badges.css | 48 ++ app/public/assets/css/components/buttons.css | 146 ++++ app/public/assets/css/components/details.css | 30 + app/public/assets/css/components/dialog.css | 17 + app/public/assets/css/components/focus.css | 11 + app/public/assets/css/components/forms.css | 112 +++ app/public/assets/css/components/header.css | 218 +++++ app/public/assets/css/components/links.css | 15 + app/public/assets/css/components/media.css | 13 + .../assets/css/components/pagination.css | 57 ++ app/public/assets/css/components/search.css | 43 + app/public/assets/css/components/tables.css | 28 + app/public/assets/css/components/toast.css | 65 ++ app/public/assets/css/form.css | 8 +- app/public/assets/css/public.css | 3 +- app/public/assets/css/repertoire.css | 3 +- app/public/assets/css/reset.css | 104 +++ app/public/assets/css/style.css | 27 + app/public/assets/css/tfe.css | 5 +- app/public/assets/css/typography.css | 91 +++ app/public/assets/css/utilities.css | 46 ++ app/public/assets/css/variables.css | 167 +--- app/public/partage/index.php | 6 +- app/public/partage/recapitulatif.php | 2 +- app/public/partage/retry-email.php | 2 +- .../ac5da541967d8b07bd0ede8543d30443.json | 2 +- .../f528764d624db129b32c21fbca0cb8d6.json | 2 +- app/storage/logs/admin.log | 2 + app/templates/admin/acces.php | 5 +- app/templates/head.php | 3 +- 36 files changed, 1254 insertions(+), 1052 deletions(-) create mode 100644 app/public/assets/css/base.css create mode 100644 app/public/assets/css/colors.css create mode 100644 app/public/assets/css/components/badges.css create mode 100644 app/public/assets/css/components/buttons.css create mode 100644 app/public/assets/css/components/details.css create mode 100644 app/public/assets/css/components/dialog.css create mode 100644 app/public/assets/css/components/focus.css create mode 100644 app/public/assets/css/components/forms.css create mode 100644 app/public/assets/css/components/header.css create mode 100644 app/public/assets/css/components/links.css create mode 100644 app/public/assets/css/components/media.css create mode 100644 app/public/assets/css/components/pagination.css create mode 100644 app/public/assets/css/components/search.css create mode 100644 app/public/assets/css/components/tables.css create mode 100644 app/public/assets/css/components/toast.css create mode 100644 app/public/assets/css/reset.css create mode 100644 app/public/assets/css/style.css create mode 100644 app/public/assets/css/typography.css create mode 100644 app/public/assets/css/utilities.css diff --git a/TODO.md b/TODO.md index 05f0d20..93a5def 100644 --- a/TODO.md +++ b/TODO.md @@ -46,6 +46,22 @@ - [x] Add Créer button to jury supervisor autocomplete (removed guard in pill-search-fragment.php) - [x] Fix: UNIQUE constraint on authors.email — findOrCreateAuthor now checks for existing author by email before inserting; prevents crash when two authors share an email +# CSS Refactoring (css-methodology-spec.md) + +- [x] Split variables.css into colors.css + typography.css +- [x] Create reset.css (Tailwind Preflight) +- [x] Create base.css (≤ 5 site-wide rules) +- [x] Create utilities.css (sr-only, skip-link, reduced-motion) +- [x] Create components/ (links, focus, forms, tables, dialog, details, media, buttons, badges, toasts, pagination, header, search) +- [x] Create style.css root @import file +- [x] Remove redundant @import url("./variables.css") from page files +- [x] Clean up duplicate status-badge / toast definitions from admin.css (now in components/) +- [x] Update head.php + partage pages to load style.css +- [x] Common.css → backward-compat wrapper importing style.css +- [x] Variables.css → backward-compat wrapper importing colors.css + typography.css +- [x] Update comment references from common.css → component files +- [ ] Verify no visual regressions + # Current tasks - [x] Mandatory auto-generated passwords on share links (no custom passwords, regenerate-only in edit, rate limit on password gate) diff --git a/app/public/assets/css/admin.css b/app/public/assets/css/admin.css index 55e294b..af8792c 100644 --- a/app/public/assets/css/admin.css +++ b/app/public/assets/css/admin.css @@ -116,7 +116,7 @@ margin-top: var(--space-l); } -/* ── Admin button aliases — see common.css .btn base class ────────────── */ +/* ── Admin button aliases — see components/buttons.css .btn base class ─── */ .admin-btn { /* deprecated alias for .btn--primary; kept for backward-compat */ @@ -138,67 +138,7 @@ /* deprecated alias for .btn--danger; kept for backward-compat */ } -/* ── Toast messages (bottom-center, CSS-only auto-fade) ─────────────── */ -#toast-region { - position: fixed; - bottom: var(--space-l); - left: 50%; - transform: translateX(-50%); - z-index: 10000; - display: flex; - flex-direction: column; - align-items: center; - gap: var(--space-xs); - pointer-events: none; - width: max-content; - max-width: min(560px, calc(100vw - 2 * var(--space-l))); -} - -.toast { - padding: var(--space-s) var(--space-m); - border-radius: var(--radius); - font-size: var(--step-0); - border-left: 4px solid; - box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25); - pointer-events: auto; - /* enter then fade out — total visible ~6.35 s */ - animation: toast-enter 0.35s ease-out, - toast-exit 0.5s ease-in 6s forwards; -} - -.toast--error { - background: var(--bg-secondary); - border-color: var(--error); - color: var(--text-primary); -} - -.toast--success { - background: var(--bg-secondary); - border-color: var(--success); - color: var(--text-primary); -} - -.toast--warning { - background: var(--bg-secondary); - border-color: var(--warning); - color: var(--text-primary); - animation: toast-enter 0.35s ease-out; /* no fade-out — stays until dismissed */ -} - -.toast--warning a { - color: inherit; - text-decoration: underline; -} - -@keyframes toast-enter { - from { opacity: 0; transform: translateY(12px); } - to { opacity: 1; transform: translateY(0); } -} - -@keyframes toast-exit { - from { opacity: 1; } - to { opacity: 0; pointer-events: none; } -} +/* ── Toast messages — styles in components/toast.css ───────────────── */ /* ── Stats cards ────────────────────────────────────────────────────────── */ .admin-stats { @@ -352,7 +292,7 @@ } /* ── Table ──────────────────────────────────────────────────────────────── */ -/* Base table/th/td styles live in common.css */ +/* Base table/th/td styles live in components/tables.css */ .admin-body main > table { margin-top: var(--space-m); } @@ -406,52 +346,10 @@ th.admin-ap-col { } /* ── Status badges ──────────────────────────────────────────────────────── */ -.status-badge { - display: inline-block; - padding: var(--space-3xs) var(--space-2xs); - border-radius: var(--radius); - font-size: var(--step--2); - font-weight: 500; - letter-spacing: 0.04em; -} +/* ── Status badges — base styles in components/badges.css ─────────── */ -.status-published { - background: var(--green-muted-bg); - color: var(--accent-green); -} - -.status-pending { - background: var(--warning-muted-bg); - color: var(--warning); -} - -.status-access { - display: inline-block; - font-size: var(--step--2); - padding: var(--space-3xs) var(--space-3xs); - border-radius: var(--radius); - background: var(--bg-tertiary); - color: var(--text-secondary); - letter-spacing: 0.03em; -} - -.status-access--libre { - background: var(--green-muted-bg); - color: var(--accent-green); -} - -.status-access--interne { - background: var(--blue-muted-bg); - color: var(--accent-blue); -} - -.status-access--interdit { - background: var(--error-muted-bg); - color: var(--error); -} - -/* ── Compact table badges ─────────────────────────────────────────── */ -.status-badge { +/* ── Compact table badges (admin-only override) ──────────────────── */ +.admin-body .status-badge { font-size: 0.7rem; padding: 2px var(--space-3xs); } @@ -541,7 +439,7 @@ th.admin-ap-col { border-color: var(--yellow-muted-border); } -/* ── Action buttons in table — see common.css .btn base class ──────────── */ +/* ── Action buttons in table — see components/buttons.css .btn base class ─ */ .admin-actions { display: flex; gap: var(--space-3xs); @@ -1030,7 +928,7 @@ th.admin-ap-col { } /* ── Dialog ───────────────────────────────────────────────────────────── */ -/* Base dialog/::backdrop styles live in common.css */ +/* Base dialog/::backdrop styles live in components/dialog.css */ .admin-dialog { max-width: 680px; width: 100%; diff --git a/app/public/assets/css/apropos.css b/app/public/assets/css/apropos.css index a9e3b47..abe5a82 100644 --- a/app/public/assets/css/apropos.css +++ b/app/public/assets/css/apropos.css @@ -1,9 +1,8 @@ /* ============================================================ À PROPOS PAGE (apropos.php) + Root class: .apropos-main ============================================================ */ -@import url("./variables.css"); - /* ------------------------------------------------------------------ */ /* Page shell */ /* ------------------------------------------------------------------ */ diff --git a/app/public/assets/css/base.css b/app/public/assets/css/base.css new file mode 100644 index 0000000..0c0d7a7 --- /dev/null +++ b/app/public/assets/css/base.css @@ -0,0 +1,44 @@ +/* ============================================================ + BASE — Minimal site-wide rules. Keep this extremely small + (≤ 5 rules). Promote a pattern from a component to base + only once it's clearly universal. + ============================================================ */ + +/* Full-height flex layout: header → main → (optional footer) */ +html, body { + height: 100%; + overflow: hidden; +} + +body { + font-family: var(--font-body); + background: var(--bg-primary); + color: var(--text-primary); + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0) 92%, + rgba(149, 87, 181, 1) 100% + ); + display: flex; + flex-direction: column; +} + +main { + flex: 1; + min-height: 0; + overflow-wrap: anywhere; +} + +main * { + overflow-wrap: anywhere; + word-break: break-word; +} + +/* Global heading scale — used by admin + public pages */ +:where(h1, h2, h3, h4, h5, h6) { + font-family: var(--font-display); + font-size: var(--step-2); + font-weight: 400; + margin: 0 0 var(--space-l) 0; + line-height: 1.15; +} diff --git a/app/public/assets/css/colors.css b/app/public/assets/css/colors.css new file mode 100644 index 0000000..868f319 --- /dev/null +++ b/app/public/assets/css/colors.css @@ -0,0 +1,83 @@ +/* ============================================================ + COLOURS — All colour values in one place. No colour value + should appear anywhere else. Add new colours here first. + ============================================================ */ + +:root { + /* Backgrounds */ + --bg-primary: #ffffff; + --bg-secondary: #f5f5f5; + --bg-tertiary: #e8e8e8; + --bg-active: #d0d0d0; + + /* Text */ + --text-primary: #111111; + --text-secondary: #666666; + --text-tertiary: #999999; + + /* Borders */ + --border-primary: #dddddd; + --border-secondary: #cccccc; + + /* Border radius */ + --radius: 10px; + + /* Status */ + --success: #5cd69d; + --error: #f25a5a; + --warning: #fbca51; + + /* Accent */ + --accent-primary: #9557b5; + --accent-secondary: #683d7f; + --accent-foreground: #ffffff; + --accent-muted: rgba(149, 87, 181, 0.12); + --accent-blue: #41adff; + --accent-green: #4caf50; + --accent-yellow: #f39c12; + --accent-red: #f25a5a; + + /* Gradient (header) */ + --gradient-1: #3c856c; + --gradient-2: #60ecb4; + --gradient-3: #e390ff; + --gradient-4: #9557b5; + + /* Header decorative */ + --header-gradient-fade: rgba(149, 87, 181, 0); + --header-shadow-strong: rgba(119, 70, 145, 1); + --header-shadow-soft: rgba(119, 70, 145, 0.8); + --header-nav-active-border: rgba(255, 255, 255, 0.6); + + /* Search error block */ + --search-error-bg: #fff0f0; + --search-error-border: #cc0000; + --search-error-color: #cc0000; + + /* System page — log/config syntax highlight */ + --sys-syntax-comment: #999999; + --sys-syntax-directive: #1a6fb5; + --sys-syntax-block: #7a2fa0; + --sys-syntax-value: #a05c00; + --sys-syntax-location: #1a7a6b; + --sys-syntax-notice: #3a6ea8; + --sys-syntax-crit: #c0392b; + + /* Muted alpha overlays — derived from semantic tokens */ + --success-muted-bg: rgba(92, 214, 157, 0.12); + --success-muted-border: rgba(92, 214, 157, 0.35); + --warning-muted-bg: rgba(251, 202, 81, 0.12); + --warning-muted-border: rgba(251, 202, 81, 0.35); + --error-muted-bg: rgba(242, 90, 90, 0.12); + --error-muted-border: rgba(242, 90, 90, 0.35); + --blue-muted-bg: rgba(65, 173, 255, 0.12); + --blue-muted-border: rgba(65, 173, 255, 0.3); + --blue-muted-bg-hover: rgba(65, 173, 255, 0.22); + --yellow-muted-bg: rgba(243, 156, 18, 0.12); + --yellow-muted-border: rgba(243, 156, 18, 0.3); + --yellow-muted-bg-hover: rgba(243, 156, 18, 0.22); + --green-muted-bg: rgba(76, 175, 80, 0.12); + --green-muted-border: rgba(76, 175, 80, 0.3); + --green-muted-bg-hover: rgba(76, 175, 80, 0.22); + --danger-border-muted: rgba(242, 90, 90, 0.35); +} diff --git a/app/public/assets/css/common.css b/app/public/assets/css/common.css index 601aa8d..74d6294 100644 --- a/app/public/assets/css/common.css +++ b/app/public/assets/css/common.css @@ -1,760 +1,7 @@ -@import url("./variables.css"); - -*, -*::before, -*::after { - box-sizing: border-box; -} - -html, -body { - margin: 0; - padding: 0; - height: 100%; - overflow: hidden; -} - -body { - font-family: var(--font-body); - background: var(--bg-primary); - color: var(--text-primary); - background: linear-gradient( - 180deg, - rgba(0, 0, 0, 0) 92%, - rgba(149, 87, 181, 1) 100% - ); - display: flex; - flex-direction: column; -} - -a { - color: inherit; - text-decoration: none; -} - -a:hover { - text-decoration-line: underline; - text-decoration-style: wavy; - text-decoration-thickness: 1px; -} - -header { - vertical-align: center; - flex-shrink: 0; - background: linear-gradient( - 180deg, - var(--gradient-1) 0%, - var(--gradient-2) 33%, - var(--gradient-3) 66%, - var(--gradient-4) 100% - ); - - .nav-logo { - text-decoration: none; - } - - .nav-left-links, - .nav-right-links { - display: flex; - gap: var(--space-l); - align-items: center; - list-style: none; - margin: 0; - padding: 0; - } - - nav { - padding: var(--space-s) var(--space-s); - display: flex; - align-items: center; - justify-content: space-between; - font-size: var(--step-0); - - a { - font-family: var(--font-display); - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--accent-foreground); - text-decoration: none; - padding: var(--space-3xs) var(--space-xs); - border-radius: var(--radius); - text-shadow: - 0 0 16px var(--header-shadow-strong), - 0 0 32px var(--header-shadow-soft); - } - - ul { - display: flex; - gap: var(--space-l); - align-items: center; - list-style: none; - margin: 0; - padding: 0; - } - - ul a { - transition: opacity 0.15s; - } - - ul a:hover { - opacity: 1; - } - } - - ul a[aria-current="page"] { - opacity: 1; - border-bottom: 1px solid var(--header-nav-active-border); - padding-bottom: 1px; - } - - /* nav-top-row: transparent wrapper at desktop - children become - direct flex items of nav, preserving the existing layout */ - .nav-top-row { - display: contents; - } - - /* nav-mobile-links: mobile-only dropdown, hidden at desktop */ - .nav-mobile-links { - display: none; /* overridden to block inside the mobile media query */ - } -} - /* ============================================================ - HAMBURGER MENU - public nav (pure CSS, checkbox trick) - - DOM order inside
(public only): - input.menu-btn ← off-screen checkbox - nav - div.nav-top-row ← always-visible row - ul.nav-left-links ← logo + Répertoire - ul.nav-right-links ← Licences, À Propos - label.menu-icon ← burger icon trigger - ul.nav-mobile-links ← full dropdown (hidden by default) - - At desktop: .menu-icon and .nav-mobile-links are display:none. - .nav-top-row is display:contents so its children - participate directly in nav's flex row. - At mobile: nav becomes a flex column. .nav-top-row is a real - flex row (logo | burger). .nav-mobile-links expands - via max-height on checkbox:checked. + COMMON — Backward-compat wrapper. All styles now live in + style.css and the component/page files it imports. + Kept so any direct references still work. ============================================================ */ -/* Off-screen checkbox - triggered by its label */ -.menu-btn { - position: absolute; - top: -9999px; - left: -9999px; -} - -/* Burger label - takes no space at desktop */ -.menu-icon { - display: none; - cursor: pointer; - padding: var(--space-2xs) var(--space-s); - align-items: center; - justify-content: center; -} - -/* Middle bar of the burger icon */ -.navicon { - background: var(--accent-foreground); - display: block; - height: 2px; - width: 24px; - position: relative; - transition: all 0.3s ease-out; -} - -/* Top and bottom bars */ -.navicon::before, -.navicon::after { - content: ""; - background: var(--accent-foreground); - display: block; - height: 2px; - width: 100%; - position: absolute; - transition: all 0.3s ease-out; -} - -.navicon::before { - top: -7px; -} -.navicon::after { - bottom: -7px; -} - -/* ---- Mobile ---- */ -@media screen and (max-width: 640px) { - /* Nav becomes a flex column: top-row on row 1, dropdown on row 2 */ - header nav[aria-label="Navigation principale"] { - display: flex; - flex-direction: column; - align-items: stretch; - padding: 0; - } - - /* Top row: logo left, hamburger right */ - header nav[aria-label="Navigation principale"] .nav-top-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--space-s); - } - - /* Hide desktop link lists inside the top row, but keep the logo visible */ - header nav[aria-label="Navigation principale"] .nav-right-links { - display: none; - } - header nav[aria-label="Navigation principale"] .nav-left-links { - display: flex; - gap: 0; - } - header nav[aria-label="Navigation principale"] - .nav-left-links - li:not(:first-child) { - display: none; - } - - /* Reveal the hamburger icon */ - .menu-icon { - display: flex; - } - - /* Dropdown: shown as block but clipped to zero height by default */ - header nav[aria-label="Navigation principale"] .nav-mobile-links { - display: block; /* override the desktop display:none */ - list-style: none; - margin: 0; - padding: 0; - max-height: 0; - overflow: hidden; - transition: max-height 0.2s ease-out; - } - - /* ---- Open state ---- */ - .menu-btn:checked - ~ nav[aria-label="Navigation principale"] - .nav-mobile-links { - max-height: 300px; - } - - /* Dropdown link rows */ - header nav[aria-label="Navigation principale"] .nav-mobile-links li { - border-top: 1px solid var(--header-nav-active-border); - text-align: left; - } - - header nav[aria-label="Navigation principale"] .nav-mobile-links li a { - display: block; - width: 100%; - padding: var(--space-s) var(--space-s); - text-align: left; - } - - /* ---- Animate burger → X ---- */ - .menu-btn:checked - ~ nav[aria-label="Navigation principale"] - .menu-icon - .navicon { - background: transparent; - } - - .menu-btn:checked - ~ nav[aria-label="Navigation principale"] - .menu-icon - .navicon::before { - transform: rotate(-45deg); - top: 0; - } - - .menu-btn:checked - ~ nav[aria-label="Navigation principale"] - .menu-icon - .navicon::after { - transform: rotate(45deg); - bottom: 0; - } -} - -main { - flex: 1; - min-height: 0; - overflow-wrap: anywhere; -} - -main * { - overflow-wrap: anywhere; - word-break: break-word; -} - -/* ============================================================ - HEADINGS - global scale, shared by admin + public pages - All headings use the same font size (slightly smaller than - the previous h1). Individual page overrides for size have - been removed so everything inherits from here. - ============================================================ */ - -:where(h1, h2, h3, h4, h5, h6) { - font-family: var(--font-display); - font-size: var(--step-2); - font-weight: 400; - margin: 0 0 var(--space-l) 0; - line-height: 1.15; -} - -/* ============================================================ - SEARCH BAR (shared) - ============================================================ */ -.header-search-wrap { - padding: 0 0; - flex-shrink: 0; - background-color: var(--gradient-4); - background: linear-gradient(180deg, var(--gradient-4) 0%, #ffffffee 100%); -} - -.header-search-form { - width: 100%; -} - -.header-search-input-wrap { - position: relative; - display: flex; - align-items: center; -} - -.header-search-icon { - position: absolute; - left: var(--space-s); - width: 18px; - height: 18px; - stroke: var(--accent-primary); - pointer-events: none; -} - -.header-search-input-wrap input { - width: 100%; - padding: var(--space-2xs) var(--space-s) !important; - padding-left: calc(18px + var(--space-l)) !important; - border: 1px solid var(--accent-primary) !important; - border-radius: var(--radius) !important; - background: var(--bg-primary) !important; - font-size: var(--step-0) !important; - color: var(--text-primary) !important; - font-family: inherit !important; -} - -.header-search-input-wrap input::placeholder { - color: var(--accent-primary) !important; -} - -/* ============================================================ - ACCESSIBILITY UTILITIES - ============================================================ */ - -/* Visually-hidden but screen-reader-accessible */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* Skip-to-content link (visible only on keyboard focus) */ -.skip-link { - position: absolute; - top: -999px; - left: 1rem; - z-index: 9999; - padding: var(--space-2xs) var(--space-s); - background: var(--accent-primary); - color: var(--text-primary); - font-size: var(--step--1); - font-weight: 600; - text-decoration: none; - border-radius: 0 0 4px 4px; -} - -.skip-link:focus { - top: 0; -} - -/* Consistent keyboard-focus ring for all interactive elements */ -:focus-visible { - outline: none; - box-shadow: 0 0 0 2px var(--accent-primary); - border-radius: var(--radius); - padding: var(--space-3xs) var(--space-xs); -} - -/* Respect user motion preferences */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - transition-duration: 0.01ms !important; - animation-duration: 0.01ms !important; - } -} - -/* ============================================================ - FORM ELEMENTS - base input / select / textarea / button -============================================================ */ - -label { - display: block; - margin-bottom: var(--space-3xs); -} - -/* ── Text inputs, selects, textareas ─────────────────────────────── */ - -input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not( - [type="hidden"] -):not([type="submit"]):not([type="button"]):not([type="reset"]):not( - [type="color"] -), -textarea { - font-family: inherit; - font-size: var(--step--1); - padding: var(--space-2xs) var(--space-xs); - border: 1px solid var(--border-primary); - border-radius: var(--radius); - background: transparent; - color: var(--text-primary); - transition: border-color 0.15s; -} - -input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not( - [type="hidden"] -):not([type="submit"]):not([type="button"]):not([type="reset"]):not( - [type="color"] -):focus, -textarea:focus { - outline: none; - border: 2px solid var(--accent-primary); -} - -input::placeholder, -textarea::placeholder { - color: var(--text-tertiary); - font-size: var(--step--1); -} - -textarea { - resize: vertical; - min-height: 80px; - line-height: 1.5; -} - -/* ── Select ──────────────────────────────────────────────────────── */ - -select { - font-family: inherit; - font-size: var(--step--1); - padding: var(--space-2xs) var(--space-xs); - padding-right: 1.75rem; - border: 1px solid var(--border-primary); - border-radius: var(--radius); - background: transparent; - color: var(--text-primary); - cursor: pointer; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23999' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 0.55rem center; - -webkit-appearance: none; - appearance: none; - transition: border-color 0.15s; -} - -select:focus { - outline: none; - border: 2px solid var(--accent-primary); -} - -/* ── Checkboxes & radios ─────────────────────────────────────────── */ - -input[type="checkbox"], -input[type="radio"] { - accent-color: var(--accent-primary); - width: 16px; - height: 16px; - cursor: pointer; - flex-shrink: 0; - margin: 0; -} - -input[type="radio"] { - border-radius: 50%; -} - -/* ============================================================ - BUTTONS - shared .btn base class - Targets both and