Compare commits
4
Commits
cd6dc96214
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db402f8192 | ||
|
|
bb45e8d6ae | ||
|
|
e78a725839 | ||
|
|
849856aecb |
No files matched your search
@@ -129,10 +129,19 @@ function buildConfigScript(config: StationConfig): string {
|
|||||||
return `<script>window.__STATION_CONFIG=${json};</script>`;
|
return `<script>window.__STATION_CONFIG=${json};</script>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Build a gradient CSS value from three color stops. */
|
||||||
|
function gradient(from: string, mid: string, to: string): string {
|
||||||
|
return `linear-gradient(135deg, ${from} 0%, ${mid} 40%, ${to} 100%)`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build inline CSS custom properties on `:root` so the first paint carries
|
* Build inline CSS custom properties on `:root` so the first paint carries
|
||||||
* the correct color scheme — eliminates the color flash that occurs when
|
* the correct color scheme — eliminates the color flash that occurs when
|
||||||
* ThemeService.apply() runs asynchronously after the API call.
|
* ThemeService.apply() runs asynchronously after the API call.
|
||||||
|
*
|
||||||
|
* Includes both flat color properties AND derived gradient variables
|
||||||
|
* (--sky-gradient, --sunset-gradient, --mixed-gradient) so components
|
||||||
|
* using var() for backgrounds render correctly on the very first paint.
|
||||||
*/
|
*/
|
||||||
function buildThemeStyle(config: StationConfig): string {
|
function buildThemeStyle(config: StationConfig): string {
|
||||||
const props: string[] = [];
|
const props: string[] = [];
|
||||||
@@ -144,6 +153,15 @@ function buildThemeStyle(config: StationConfig): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derived gradients — mirror the same formulas ThemeService.apply() uses.
|
||||||
|
// These must be injected server-side; otherwise components referencing
|
||||||
|
// var(--sky-gradient) etc. fall back to their SCSS build-time defaults
|
||||||
|
// on first paint, causing a visible color flash.
|
||||||
|
const cp = (field: keyof StationConfig): string => (config as any)[field] || '';
|
||||||
|
props.push(`--sky-gradient: ${gradient(cp('color_primary_dark'), cp('color_primary'), cp('color_primary_light'))}`);
|
||||||
|
props.push(`--sunset-gradient: ${gradient(cp('color_accent_dark'), cp('color_accent'), cp('color_accent_light'))}`);
|
||||||
|
props.push(`--mixed-gradient: linear-gradient(135deg, ${cp('color_primary_dark')} 0%, ${cp('color_primary')} 50%, ${cp('color_accent')} 100%)`);
|
||||||
|
|
||||||
if (props.length === 0) return '';
|
if (props.length === 0) return '';
|
||||||
|
|
||||||
const css = `:root{${props.join(';')}}`;
|
const css = `:root{${props.join(';')}}`;
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
.about-cta {
|
.about-cta {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: $spacing-2xl $spacing-lg;
|
padding: $spacing-2xl $spacing-lg;
|
||||||
background: $mixed-gradient;
|
background: var(--mixed-gradient);
|
||||||
border-radius: $radius-xl;
|
border-radius: $radius-xl;
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
|
|
||||||
|
|||||||
@@ -1,141 +1,23 @@
|
|||||||
@use '../../styles/variables' as *;
|
@use '../../styles/variables' as *;
|
||||||
@use '../../styles/mixins' as *;
|
@use '../../styles/mixins' as *;
|
||||||
|
|
||||||
// ── Alert banners ──────────────────────────────────────────────
|
.add-admin-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border: 1px solid var(--color-light, #e0e0e0);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
.form-error {
|
h3 {
|
||||||
background: rgba($danger-red, 0.1);
|
margin: 0 0 1rem 0;
|
||||||
color: $danger-red;
|
}
|
||||||
padding: $spacing-sm $spacing-md;
|
|
||||||
border-radius: $radius-md;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-bottom: $spacing-md;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-success {
|
|
||||||
background: rgba($success-green, 0.1);
|
|
||||||
color: $success-green;
|
|
||||||
padding: $spacing-sm $spacing-md;
|
|
||||||
border-radius: $radius-md;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-bottom: $spacing-md;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Admin users list ───────────────────────────────────────────
|
|
||||||
|
|
||||||
.admin-users-section {
|
.admin-users-section {
|
||||||
margin-bottom: $spacing-lg;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-text {
|
.loading-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: $spacing-xl;
|
padding: 2rem;
|
||||||
color: $neutral-medium;
|
color: var(--color-medium, #888);
|
||||||
}
|
|
||||||
|
|
||||||
// ── Add admin form ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
.add-admin-section {
|
|
||||||
margin-top: $spacing-lg;
|
|
||||||
padding: $spacing-lg;
|
|
||||||
border: 1px solid $neutral-light;
|
|
||||||
border-radius: $radius-lg;
|
|
||||||
background: $neutral-white;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-family: $font-heading;
|
|
||||||
color: $primary-blue;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 0 $spacing-md 0;
|
|
||||||
padding-bottom: $spacing-xs;
|
|
||||||
border-bottom: 1px solid $neutral-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-form {
|
|
||||||
// ── Layout ──────────────────────────────────────────
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: $spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Field group ─────────────────────────────────────
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: $spacing-md;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $neutral-dark;
|
|
||||||
margin-bottom: $spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: $spacing-sm $spacing-md;
|
|
||||||
border: 1px solid $neutral-light;
|
|
||||||
border-radius: $radius-md;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-family: $font-primary;
|
|
||||||
color: $neutral-dark;
|
|
||||||
background: $neutral-white;
|
|
||||||
transition: border-color $transition-fast, box-shadow $transition-fast;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $neutral-medium;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $primary-blue;
|
|
||||||
box-shadow: 0 0 0 3px rgba($primary-blue, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-invalid {
|
|
||||||
border-color: $danger-red;
|
|
||||||
box-shadow: 0 0 0 3px rgba($danger-red, 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Actions bar ─────────────────────────────────────
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: $spacing-sm;
|
|
||||||
margin-top: $spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-save {
|
|
||||||
@include button-style($primary-blue, $neutral-white, $primary-blue-light);
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Responsive ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@include responsive(md) {
|
|
||||||
.admin-form {
|
|
||||||
.form-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-admin-section {
|
|
||||||
padding: $spacing-md;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
.event-date {
|
.event-date {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: $mixed-gradient;
|
background: var(--mixed-gradient);
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
.login-section {
|
.login-section {
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
min-height: calc(100vh - 80px);
|
min-height: calc(100vh - 80px);
|
||||||
background: $sky-gradient;
|
background: var(--sky-gradient);
|
||||||
padding: $spacing-xl;
|
padding: $spacing-xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,13 +21,13 @@
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-family: $font-heading;
|
font-family: $font-heading;
|
||||||
color: $primary-blue;
|
color: var(--color-primary);
|
||||||
margin: $spacing-sm 0 $spacing-xs;
|
margin: $spacing-sm 0 $spacing-xs;
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $neutral-medium;
|
color: var(--color-medium);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,14 +49,14 @@
|
|||||||
display: block;
|
display: block;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $neutral-dark;
|
color: var(--color-dark);
|
||||||
margin-bottom: $spacing-xs;
|
margin-bottom: $spacing-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: $spacing-sm $spacing-md;
|
padding: $spacing-sm $spacing-md;
|
||||||
border: 1px solid $neutral-light;
|
border: 1px solid var(--color-light);
|
||||||
border-radius: $radius-md;
|
border-radius: $radius-md;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: border-color $transition-fast;
|
transition: border-color $transition-fast;
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: $primary-blue;
|
border-color: var(--color-primary);
|
||||||
box-shadow: 0 0 0 3px rgba($primary-blue, 0.15);
|
box-shadow: 0 0 0 3px rgba($primary-blue, 0.15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
|
|
||||||
.login-error {
|
.login-error {
|
||||||
background: rgba($danger-red, 0.1);
|
background: rgba($danger-red, 0.1);
|
||||||
color: $danger-red;
|
color: var(--color-danger);
|
||||||
padding: $spacing-sm $spacing-md;
|
padding: $spacing-sm $spacing-md;
|
||||||
border-radius: $radius-md;
|
border-radius: $radius-md;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-login {
|
.btn-login {
|
||||||
@include button-style($primary-blue, $neutral-white, $primary-blue-light);
|
@include button-style(var(--color-primary), var(--color-white), var(--color-primary-light));
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: $spacing-sm $spacing-lg;
|
padding: $spacing-sm $spacing-lg;
|
||||||
margin-top: $spacing-sm;
|
margin-top: $spacing-sm;
|
||||||
|
|||||||
+7
-25
@@ -3,33 +3,15 @@
|
|||||||
// ========== CSS Custom Properties (exposed to the browser) ==========
|
// ========== CSS Custom Properties (exposed to the browser) ==========
|
||||||
// These map SCSS variables to CSS custom properties so Angular components
|
// These map SCSS variables to CSS custom properties so Angular components
|
||||||
// can consume them via var() at runtime.
|
// can consume them via var() at runtime.
|
||||||
|
//
|
||||||
|
// COLOR variables (color_*, gradient_*) are NOT set here anymore.
|
||||||
|
// They are injected at runtime by the SSR server as an inline <style> block
|
||||||
|
// so the first paint always carries the correct station colors.
|
||||||
|
// Client-side ThemeService.apply() keeps them in sync after Angular boots.
|
||||||
|
//
|
||||||
|
// Non-color variables (fonts, radii, shadows) remain below as build-time defaults.
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
// Primary
|
|
||||||
--color-primary: #{$primary-blue};
|
|
||||||
--color-primary-light: #{$primary-blue-light};
|
|
||||||
--color-primary-dark: #{$primary-blue-dark};
|
|
||||||
--color-primary-muted: #{$primary-blue-muted};
|
|
||||||
|
|
||||||
// Accent
|
|
||||||
--color-accent: #{$accent-orange};
|
|
||||||
--color-accent-light: #{$accent-orange-light};
|
|
||||||
--color-accent-dark: #{$accent-orange-dark};
|
|
||||||
--color-accent-muted: #{$accent-orange-muted};
|
|
||||||
|
|
||||||
// Neutrals
|
|
||||||
--color-white: #{$neutral-white};
|
|
||||||
--color-cream: #{$neutral-cream};
|
|
||||||
--color-light: #{$neutral-light};
|
|
||||||
--color-medium: #{$neutral-medium};
|
|
||||||
--color-dark: #{$neutral-dark};
|
|
||||||
--color-black: #{$neutral-black};
|
|
||||||
|
|
||||||
// Semantic
|
|
||||||
--color-success: #{$success-green};
|
|
||||||
--color-danger: #{$danger-red};
|
|
||||||
--color-info: #{$info-blue};
|
|
||||||
|
|
||||||
// Typography
|
// Typography
|
||||||
--font-primary: #{$font-primary};
|
--font-primary: #{$font-primary};
|
||||||
--font-heading: #{$font-heading};
|
--font-heading: #{$font-heading};
|
||||||
|
|||||||
Reference in new issue
Block a user