This commit is contained in:
edsea
2025-10-11 00:21:00 +02:00
commit f0c9786164
27 changed files with 10684 additions and 0 deletions

60
.htaccess Normal file
View File

@@ -0,0 +1,60 @@
# Enable RewriteEngine
RewriteEngine On
RewriteBase /
# Add trailing slash to URLs (except files and directories)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.+)$ $1/ [L,R=301]
# Handle 404 errors
ErrorDocument 404 /404
# Rewrite pretty URLs to index.php?page=
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)/$ index.php?page=$1 [L,QSA]
# Enable GZip compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE application/xml application/xhtml+xml application/rss+xml
AddOutputFilterByType DEFLATE application/font-woff application/font-woff2
AddOutputFilterByType DEFLATE image/svg+xml image/webp
</IfModule>
# Enable browser caching for static assets
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
</IfModule>
# Security headers
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
</IfModule>
# Prevent directory listing
Options -Indexes
# BEGIN cPanel-generated php ini directives, do not edit
<IfModule php8_module>
php_value output_buffering Off
</IfModule>
<IfModule lsapi_module>
php_value output_buffering Off
</IfModule>
# END cPanel-generated php ini directives, do not edit

6
index.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/pageHandler.php');
include($_SERVER['DOCUMENT_ROOT'] . '/rss/php/includes/header.php');
loadContents();
include($_SERVER['DOCUMENT_ROOT'] . '/rss/php/includes/footer.php');
?>

3
robots.txt Normal file
View File

@@ -0,0 +1,3 @@
User-agent: *
Disallow: /404
Sitemap: https://DOMAIN.COM/sitemap.xml

6792
rss/css/main.css Normal file

File diff suppressed because it is too large Load Diff

1
rss/css/main.css.map Normal file

File diff suppressed because one or more lines are too long

873
rss/css/main.scss Normal file
View File

@@ -0,0 +1,873 @@
@import 'variables';
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
/* ========================================================================== */
/* Reset + Base */
/* ========================================================================== */
*,
*::before,
*::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
font-family: $font-family-base;
font-size: map-get($font-sizes, base);
line-height: map-get($line-heights, normal);
color: map-get($colors, dark);
background: map-get($colors, white);
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
img, svg, video, canvas { display: block; max-width: 100%; height: auto; }
/* ========================================================================== */
/* Typography Utilities */
/* ========================================================================== */
@each $k, $v in $font-sizes { .text-#{$k} { font-size: $v; } }
@each $k, $v in $font-weights { .fw-#{$k} { font-weight: $v; } }
@each $k, $v in $line-heights { .lh-#{$k} { line-height: $v; } }
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-uppercase { text-transform: uppercase; }
.text-lowercase { text-transform: lowercase; }
.text-capitalize { text-transform: capitalize; }
.text-underline { text-decoration: underline; }
.text-line-through { text-decoration: line-through; }
.text-no-decoration { text-decoration: none; }
/* ========================================================================== */
/* Color Utilities */
/* ========================================================================== */
@each $name, $color in $colors {
.text-#{$name} { color: $color !important; }
.bg-#{$name} { background-color: $color !important; }
.border-#{$name} { border-color: $color !important; }
}
/* ========================================================================== */
/* Spacing Utilities (Margin / Padding) */
/* ========================================================================== */
@each $k, $v in $space {
.m-#{$k} { margin: $v !important; }
.mt-#{$k} { margin-top: $v !important; }
.mr-#{$k} { margin-right: $v !important; }
.mb-#{$k} { margin-bottom: $v !important; }
.ml-#{$k} { margin-left: $v !important; }
.mx-#{$k} { margin-left: $v !important; margin-right: $v !important; }
.my-#{$k} { margin-top: $v !important; margin-bottom: $v !important; }
.p-#{$k} { padding: $v !important; }
.pt-#{$k} { padding-top: $v !important; }
.pr-#{$k} { padding-right: $v !important; }
.pb-#{$k} { padding-bottom: $v !important; }
.pl-#{$k} { padding-left: $v !important; }
.px-#{$k} { padding-left: $v !important; padding-right: $v !important; }
.py-#{$k} { padding-top: $v !important; padding-bottom: $v !important; }
}
/* Responsive spacing */
@each $bp, $w in $breakpoints {
@media (min-width: $w) {
@each $k, $v in $space {
.#{$bp}-m-#{$k} { margin: $v !important; }
.#{$bp}-mt-#{$k} { margin-top: $v !important; }
.#{$bp}-mr-#{$k} { margin-right: $v !important; }
.#{$bp}-mb-#{$k} { margin-bottom: $v !important; }
.#{$bp}-ml-#{$k} { margin-left: $v !important; }
.#{$bp}-mx-#{$k} { margin-left: $v !important; margin-right: $v !important; }
.#{$bp}-my-#{$k} { margin-top: $v !important; margin-bottom: $v !important; }
.#{$bp}-p-#{$k} { padding: $v !important; }
.#{$bp}-pt-#{$k} { padding-top: $v !important; }
.#{$bp}-pr-#{$k} { padding-right: $v !important; }
.#{$bp}-pb-#{$k} { padding-bottom: $v !important; }
.#{$bp}-pl-#{$k} { padding-left: $v !important; }
.#{$bp}-px-#{$k} { padding-left: $v !important; padding-right: $v !important; }
.#{$bp}-py-#{$k} { padding-top: $v !important; padding-bottom: $v !important; }
}
}
}
/* ========================================================================== */
/* Sizing Utilities (Width / Height) */
/* ========================================================================== */
@each $k, $v in $size-percent { .w-#{$k} { width: $v; } .h-#{$k} { height: $v; } }
@each $k, $v in $size-px { .wpx-#{$k} { width: $v; } .hpx-#{$k} { height: $v; } }
@each $k, $v in $size-rem { .wrem-#{$k} { width: $v; } .hrem-#{$k} { height: $v; } }
@each $k, $v in $size-vw { .wvw-#{$k} { width: $v; } }
@each $k, $v in $size-vh { .hvh-#{$k} { height: $v; } }
@each $k, $v in $size-dvh { .hdvh-#{$k} { height: $v; } }
@each $k, $v in $size-svh { .hsvh-#{$k} { height: $v; } }
@each $k, $v in $size-lvh { .hlvh-#{$k} { height: $v; } }
/* Responsive sizing */
@each $bp, $w in $breakpoints {
@media (min-width: $w) {
@each $k, $v in $size-percent { .#{$bp}-w-#{$k} { width: $v; } .#{$bp}-h-#{$k} { height: $v; } }
}
}
/* ========================================================================== */
/* Display + Layout */
/* ========================================================================== */
@each $t in $display-types { .d-#{$t} { display: $t !important; } }
.container {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
@each $bp, $mw in $container-max-widths {
@media (min-width: map-get($breakpoints, $bp)) { max-width: $mw; }
}
}
/* Container variants */
.container-fluid {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: none;
}
.container-bleed {
width: 100vw;
margin-left: 50%;
transform: translateX(-50%);
padding-inline: 0;
}
.container-narrow {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: clamp(36rem, 70vw, 56rem);
}
.container-wide {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: clamp(72rem, 92vw, 100rem);
}
.container-fluid {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: none;
}
.container-bleed {
width: 100vw;
margin-left: 50%;
transform: translateX(-50%);
padding-inline: 0;
}
.container-narrow {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: clamp(36rem, 70vw, 56rem);
}
.container-wide {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: clamp(72rem, 92vw, 100rem);
}
.container-fluid {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: none;
}
.container-bleed {
width: 100vw;
margin-left: 50%;
transform: translateX(-50%);
padding-inline: 0;
}
.container-narrow {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: clamp(36rem, 70vw, 56rem);
}
.container-wide {
width: 100%;
margin-inline: auto;
padding-inline: $container-padding;
max-width: clamp(72rem, 92vw, 100rem);
}
/* Flex grid */
.row {
display: flex;
flex-wrap: wrap;
margin-inline: map-get($gutters, 3);
margin-block: map-get($gutters, 3);
}
.col { flex: 1 0 0%; padding-inline: map-get($gutters, 3); padding-block: map-get($gutters, 3); }
.col-auto { flex: 0 0 auto; width: auto; padding-inline: map-get($gutters, 3); padding-block: map-get($gutters, 3); }
@for $i from 1 through $grid-columns {
.col-#{$i} {
flex: 0 0 percentage($i / $grid-columns);
max-width: percentage($i / $grid-columns);
padding-inline: map-get($gutters, 3);
padding-block: map-get($gutters, 3);
}
}
@each $bp, $w in $breakpoints {
@media (min-width: $w) {
.col-#{$bp} { flex: 1 0 0%; padding-inline: map-get($gutters, 3); padding-block: map-get($gutters, 3); }
.col-#{$bp}-auto { flex: 0 0 auto; width: auto; padding-inline: map-get($gutters, 3); padding-block: map-get($gutters, 3); }
@for $i from 1 through $grid-columns {
.col-#{$bp}-#{$i} {
flex: 0 0 percentage($i / $grid-columns);
max-width: percentage($i / $grid-columns);
padding-inline: map-get($gutters, 3);
padding-block: map-get($gutters, 3);
}
}
}
}
/* Flex helpers */
.flex { display: flex; }
.inline-flex { display: inline-flex; }
@each $k, $v in $flex-directions { .flex-#{$k} { display: flex; flex-direction: $v; } }
@each $k, $v in $justify-content-values { .justify-#{$k} { display: flex; justify-content: $v; } }
@each $k, $v in $align-items-values { .items-#{$k} { display: flex; align-items: $v; } }
/* Borders + Radius + Shadows */
@each $k, $v in $border-widths { .border-#{$k} { border-width: $v; border-style: solid; border-color: currentColor; } }
.border { border: 1px solid currentColor; }
.border-top { border-top: 1px solid currentColor; }
.border-right { border-right: 1px solid currentColor; }
.border-bottom { border-bottom: 1px solid currentColor; }
.border-left { border-left: 1px solid currentColor; }
@each $k, $v in $radii { .rounded-#{$k} { border-radius: $v; } }
.rounded { border-radius: map-get($radii, md); }
.rounded-top { border-top-left-radius: map-get($radii, md); border-top-right-radius: map-get($radii, md); }
.rounded-bottom { border-bottom-left-radius: map-get($radii, md); border-bottom-right-radius: map-get($radii, md); }
.rounded-left { border-top-left-radius: map-get($radii, md); border-bottom-left-radius: map-get($radii, md); }
.rounded-right { border-top-right-radius: map-get($radii, md); border-bottom-right-radius: map-get($radii, md); }
@each $k, $v in $shadows { .shadow-#{$k} { box-shadow: $v !important; } }
@each $k, $v in $drop-shadows { .drop-shadow-#{$k} { box-shadow: $v !important; } }
/* Z-Index */
.z-base { z-index: map-get($z, base); }
.z-dropdown { z-index: map-get($z, dropdown); }
.z-overlay { z-index: map-get($z, overlay); }
.z-modal { z-index: map-get($z, modal); }
.z-toast { z-index: map-get($z, toast); }
/* ========================================================================== */
/* Grid System (CSS Grid Utilities) */
/* ========================================================================== */
.grid { display: grid !important; }
.inline-grid { display: inline-grid !important; }
@for $i from 1 through 12 { .grid-cols-#{$i} { grid-template-columns: repeat($i, 1fr) !important; } }
.grid-cols-none { grid-template-columns: none !important; }
@each $bp, $width in $breakpoints {
@media (min-width: $width) {
@for $i from 1 through 12 {
.#{$bp}-grid-cols-#{$i} { grid-template-columns: repeat($i, 1fr) !important; }
}
}
}
@for $i from 1 through 12 { .grid-rows-#{$i} { grid-template-rows: repeat($i, 1fr) !important; } }
.grid-rows-none { grid-template-rows: none !important; }
.grid-auto-fit-sm { grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)) !important; }
.grid-auto-fit-md { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)) !important; }
.grid-auto-fit-lg { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)) !important; }
.grid-auto-fill-sm { grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)) !important; }
.grid-auto-fill-md { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)) !important; }
.grid-auto-fill-lg { grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)) !important; }
.grid-align-center { align-items: center !important; justify-items: center !important; }
.grid-align-start { align-items: start !important; justify-items: start !important; }
.grid-align-end { align-items: end !important; justify-items: end !important; }
.grid-align-stretch { align-items: stretch !important; justify-items: stretch !important; }
.place-self-center { place-self: center !important; }
.place-self-start { place-self: start !important; }
.place-self-end { place-self: end !important; }
.place-self-stretch { place-self: stretch !important; }
.grid-flow-row { grid-auto-flow: row !important; }
.grid-flow-col { grid-auto-flow: column !important; }
.grid-flow-row-dense { grid-auto-flow: row dense !important; }
.grid-flow-col-dense { grid-auto-flow: column dense !important; }
/* Auto rows/cols sizing */
.grid-auto-cols-min { grid-auto-columns: min-content !important; }
.grid-auto-cols-max { grid-auto-columns: max-content !important; }
.grid-auto-cols-fr { grid-auto-columns: 1fr !important; }
.grid-auto-rows-min { grid-auto-rows: min-content !important; }
.grid-auto-rows-max { grid-auto-rows: max-content !important; }
.grid-auto-rows-fr { grid-auto-rows: 1fr !important; }
/* ========================================================================== */
/* Control Blueprints (shared by inputs/buttons/etc.) */
/* ========================================================================== */
@mixin control-base {
appearance: none;
border-style: map-get($control-border, style);
border-width: map-get($control-border, width);
border-color: map-get($control-colors, border);
background-color: map-get($control-colors, bg);
color: map-get($control-colors, fg);
outline: 0;
transition:
background-color map-get($timings, base) map-get($easings, in-out),
color map-get($timings, base) map-get($easings, in-out),
border-color map-get($timings, base) map-get($easings, in-out),
box-shadow map-get($timings, base) map-get($easings, in-out),
transform map-get($timings, fast) map-get($easings, out);
}
@mixin control-size($size) {
$props: map-get($control-sizes, $size);
font-size: map-get($props, font-size);
line-height: map-get($props, line-height);
padding: map-get($props, py) map-get($props, px);
border-radius: map-get($props, radius);
}
/* Inputs */
.input, select, textarea {
@include control-base;
width: 100%;
&:focus {
border-color: map-get($control-colors, focus);
box-shadow: 0 0 0 3px map-get($control-colors, ring);
background-color: map-get($colors, white);
}
&:disabled {
background-color: map-get($control-colors, disabled-bg);
opacity: .6;
cursor: not-allowed;
}
&::placeholder { color: map-get($control-colors, placeholder); }
}
@each $size, $props in $control-sizes { .input-#{$size} { @include control-size($size); } }
/* Buttons */
.btn {
@include control-base;
display: inline-flex;
width: auto;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
box-shadow: map-get($shadows, 1);
&:focus-visible {
border-color: map-get($control-colors, focus);
box-shadow: 0 0 0 3px map-get($control-colors, ring), map-get($shadows, 2);
}
&:active { transform: translateY(1px); box-shadow: map-get($shadows, 3); }
&:disabled { opacity: .6; cursor: not-allowed; pointer-events: none; }
}
@each $size, $props in $control-sizes { .btn-#{$size} { @include control-size($size); } }
@each $name, $color in $colors {
.btn-#{$name} {
background-color: $color;
color: if(lightness($color) > 60%, map-get($colors, dark), map-get($colors, white));
border-color: transparent;
&:hover { background-color: darken($color, 8%); box-shadow: map-get($shadows, 2); }
}
.btn-outline-#{$name} {
background-color: transparent;
color: $color;
border-color: $color;
&:hover { background-color: $color; color: if(lightness($color) > 60%, map-get($colors, dark), map-get($colors, white)); }
}
.btn-ghost-#{$name} {
background-color: transparent;
color: $color;
border-color: transparent;
&:hover { background-color: rgba($color, .12); }
}
}
/* ========================================================================== */
/* Alerts */
/* ========================================================================== */
.alert {
padding: map-get($space, 2) map-get($space, 3);
border-radius: map-get($radii, md);
border: 1px solid map-get($control-colors, border);
background: map-get($colors, accent-white);
}
@each $name, $color in $colors {
.alert-#{$name} {
background: mix($color, map-get($colors, white), 12%);
border-color: $color;
color: if(lightness($color) > 60%, map-get($colors, dark), map-get($colors, dark));
}
}
/* ========================================================================== */
/* Cards (with custom borders and shadows) */
/* ========================================================================== */
.card {
background: map-get($colors, white);
border: 1px solid map-get($control-colors, border);
border-radius: map-get($radii, lg);
box-shadow: map-get($shadows, 1);
transition: box-shadow map-get($timings, base) map-get($easings, in-out);
}
.card-hover:hover { box-shadow: map-get($shadows, 3); }
@each $k, $v in $shadows { .card-shadow-#{$k} { box-shadow: $v; } }
@each $name, $color in $colors {
.card-border-#{$name} { border-color: $color; }
.card-bg-#{$name} { background-color: $color; }
}
/* ========================================================================== */
/* Modals */
/* ========================================================================== */
.modal {
position: fixed;
inset: 0;
display: none;
align-items: center;
justify-content: center;
padding: map-get($space, 3);
z-index: map-get($z, modal);
}
.modal.open { display: flex; }
.modal__overlay { position: absolute; inset: 0; background: rgba(map-get($colors, black), .5); }
.modal__dialog {
position: relative;
width: min(720px, 100%);
background: map-get($colors, white);
border: 1px solid map-get($control-colors, border);
border-radius: map-get($radii, lg);
box-shadow: map-get($shadows, 5);
padding: map-get($space, 3);
}
.modal__header { display: flex; align-items: center; justify-content: space-between; gap: map-get($space, 2); margin-bottom: map-get($space, 2); }
.modal__title { font-size: map-get($font-sizes, lg); font-weight: map-get($font-weights, semibold); }
.modal__close { @extend .btn; @extend .btn-sm; background: transparent; box-shadow: none; }
/* ========================================================================== */
/* Pills */
/* ========================================================================== */
.pill {
display: inline-flex;
align-items: center;
gap: map-get($space, 1);
padding: map-get($space, 1) map-get($space, 2);
border-radius: map-get($radii, full);
border: 1px solid map-get($control-colors, border);
background: map-get($colors, accent-white);
font-size: map-get($font-sizes, sm);
line-height: map-get($line-heights, normal);
}
@each $name, $color in $colors {
.pill-#{$name} {
background: mix($color, map-get($colors, white), 12%);
color: if(lightness($color) > 60%, map-get($colors, dark), map-get($colors, white));
border-color: $color;
}
}
/* ========================================================================== */
/* Accordions */
/* ========================================================================== */
.accordion { border: 1px solid map-get($control-colors, border); border-radius: map-get($radii, lg); overflow: hidden; }
.accordion__item + .accordion__item { border-top: 1px solid map-get($control-colors, border); }
.accordion__header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: map-get($space, 2);
padding: map-get($space, 2) map-get($space, 3);
cursor: pointer;
background: map-get($colors, accent-white);
border: none;
}
.accordion__title { font-weight: map-get($font-weights, medium); }
.accordion__icon { transition: transform map-get($timings, base) map-get($easings, in-out); }
.accordion__panel {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows map-get($timings, base) map-get($easings, in-out);
}
.accordion__content { overflow: hidden; padding: 0 map-get($space, 3); }
.accordion__item.is-open .accordion__panel { grid-template-rows: 1fr; }
.accordion__item.is-open .accordion__content { padding: map-get($space, 2) map-get($space, 3) map-get($space, 3); }
.accordion__item.is-open .accordion__icon { transform: rotate(180deg); }
/* ========================================================================== */
/* Stack Utilities */
/* ========================================================================== */
.stack > * + * { margin-top: map-get($space, 3); }
@each $k, $v in $space { .stack-#{$k} > * + * { margin-top: $v; } }
/* ========================================================================== */
/* Transitions & Animations */
/* ========================================================================== */
.transition-all { transition: all map-get($timings, base) map-get($easings, in-out); }
.transition-fast { transition-duration: map-get($timings, fast); }
.transition-slow { transition-duration: map-get($timings, slow); }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide-up { from { transform: translateY(10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.animate-fade-in { animation: fade-in .3s map-get($easings, out) both; }
.animate-slide-up { animation: slide-up .35s map-get($easings, out) both; }
/* ========================================================================== */
/* Tooltips */
/* ========================================================================== */
.tooltip { position: relative; display: inline-block; cursor: help; }
.tooltip::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
background: map-get($colors, dark);
color: map-get($colors, white);
padding: .25rem .5rem;
font-size: map-get($font-sizes, sm);
border-radius: map-get($radii, sm);
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity map-get($timings, base) map-get($easings, in-out);
z-index: map-get($z, dropdown);
}
.tooltip:hover::after { opacity: 1; }
/* ========================================================================== */
/* Tabs */
/* ========================================================================== */
.tabs { display: flex; gap: map-get($space, 2); border-bottom: 1px solid map-get($control-colors, border); }
.tab {
padding: map-get($space, 2) map-get($space, 3);
cursor: pointer;
border-bottom: 2px solid transparent;
transition:
border-color map-get($timings, base) map-get($easings, in-out),
color map-get($timings, base) map-get($easings, in-out);
}
.tab.active { border-color: map-get($colors, primary); color: map-get($colors, primary); }
/* ========================================================================== */
/* Transform & Scale Utilities */
/* ========================================================================== */
.scale-90 { transform: scale(.9); }
.scale-95 { transform: scale(.95); }
.scale-100 { transform: scale(1); }
.scale-105 { transform: scale(1.05); }
.scale-110 { transform: scale(1.1); }
.rotate-90 { transform: rotate(90deg); }
.rotate-180 { transform: rotate(180deg); }
.rotate-270 { transform: rotate(270deg); }
.skew-x-6 { transform: skewX(6deg); }
.skew-y-6 { transform: skewY(6deg); }
/* ========================================================================== */
/* Shadow, Blur, and Glow Utilities */
/* ========================================================================== */
.shadow-none { box-shadow: none !important; }
.blur-sm { filter: blur(2px); }
.blur-md { filter: blur(4px); }
.blur-lg { filter: blur(8px); }
.glow { box-shadow: 0 0 12px rgba(map-get($colors, accent), .5); }
/* ========================================================================== */
/* Dark Mode (opt-in via .theme-dark) */
/* ========================================================================== */
.theme-dark { background: map-get($colors, dark); color: map-get($colors, light); }
.theme-dark .card,
.theme-dark .modal__dialog {
background: map-get($colors, secondary);
border-color: map-get($colors, muted);
color: map-get($colors, light);
}
.theme-dark .input,
.theme-dark select,
.theme-dark textarea {
background: map-get($colors, secondary);
color: map-get($colors, light);
border-color: map-get($colors, muted);
}
.theme-dark .tabs { border-bottom-color: map-get($colors, muted); }
.theme-dark .tab.active { border-color: map-get($colors, primary); color: map-get($colors, primary); }
/* ========================================================================== */
/* Scroll Utilities */
/* ========================================================================== */
.scroll-y { overflow-y: auto !important; }
.scroll-x { overflow-x: auto !important; }
.scroll-hidden { overflow: hidden !important; }
/* ========================================================================== */
/* Spinner */
/* ========================================================================== */
@keyframes ui-spinner { to { transform: rotate(360deg); } }
.spinner {
display: inline-block;
width: 1rem;
height: 1rem;
border-radius: 50%;
border-style: solid;
border-width: 2px;
border-color: map-get($colors, dark);
border-left-color: transparent !important;
animation: ui-spinner 1s linear infinite;
}
@each $name, $color in $colors {
.spinner-#{$name} { border-color: $color; border-left-color: transparent !important; }
}
.spinner-sm { width: .875rem; height: .875rem; border-width: 2px; }
.spinner-md { width: 1.25rem; height: 1.25rem; border-width: 2px; }
.spinner-lg { width: 1.75rem; height: 1.75rem; border-width: 3px; }
.spinner-xl { width: 2.25rem; height: 2.25rem; border-width: 4px; }
/* ========================================================================== */
/* Visibility + Accessibility */
/* ========================================================================== */
.visible { visibility: visible !important; }
.invisible { visibility: hidden !important; }
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
/* ========================================================================== */
/* Overflow + Text Truncation */
/* ========================================================================== */
.overflow-hidden { overflow: hidden !important; }
.overflow-auto { overflow: auto !important; }
.overflow-scroll { overflow: scroll !important; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ========================================================================== */
/* Opacity */
/* ========================================================================== */
.opacity-0 { opacity: 0; }
.opacity-25 { opacity: .25; }
.opacity-50 { opacity: .5; }
.opacity-75 { opacity: .75; }
.opacity-100 { opacity: 1; }
/* ========================================================================== */
/* Cursor + Pointer Events */
/* ========================================================================== */
.cursor-default { cursor: default !important; }
.cursor-pointer { cursor: pointer !important; }
.cursor-not-allowed { cursor: not-allowed !important; }
.pointer-none { pointer-events: none !important; }
.pointer-auto { pointer-events: auto !important; }
/* ========================================================================== */
/* Position + Inset */
/* ========================================================================== */
.relative { position: relative !important; }
.absolute { position: absolute !important; }
.fixed { position: fixed !important; }
.sticky { position: sticky !important; }
.inset-0 { inset: 0 !important; }
.top-0 { top: 0 !important; }
.right-0 { right: 0 !important; }
.bottom-0 { bottom: 0 !important; }
.left-0 { left: 0 !important; }
/* ========================================================================== */
/* Flex Wrapping + Gap */
/* ========================================================================== */
.wrap { flex-wrap: wrap !important; }
.nowrap { flex-wrap: nowrap !important; }
.wrap-reverse { flex-wrap: wrap-reverse !important; }
@each $k, $v in $space {
.gap-#{$k} { gap: $v !important; }
.row-gap-#{$k} { row-gap: $v !important; }
.col-gap-#{$k} { column-gap: $v !important; }
}
/* ========================================================================== */
/* Responsive Reverse (small-down) */
/* ========================================================================== */
.sm-reverse {
@media (max-width: map-get($breakpoints, md)) {
display: flex;
flex-direction: column-reverse;
}
}
/* ========================================================================== */
/* Aspect Ratios + Object Fit */
/* ========================================================================== */
.ratio { position: relative; width: 100%; height: 0; overflow: hidden; }
.ratio > * { position: absolute; inset: 0; width: 100%; height: 100%; }
.ratio-16x9 { padding-bottom: 56.25%; }
.ratio-4x3 { padding-bottom: 75%; }
.ratio-1x1 { padding-bottom: 100%; }
.object-contain { object-fit: contain !important; }
.object-cover { object-fit: cover !important; }
.object-fill { object-fit: fill !important; }
.object-none { object-fit: none !important; }
.object-scale-down { object-fit: scale-down !important; }
/* ========================================================================== */
/* Hover Motion Helpers */
/* ========================================================================== */
.hover-raise { transition: transform map-get($timings, base) map-get($easings, in-out), box-shadow map-get($timings, base) map-get($easings, in-out); }
.hover-raise:hover { transform: translateY(-2px); box-shadow: map-get($shadows, 2); }
.hover-glow { transition: box-shadow map-get($timings, base) map-get($easings, in-out); }
.hover-glow:hover { box-shadow: 0 0 0 3px map-get($control-colors, ring), map-get($shadows, 2); }
/* ========================================================================== */
/* Divider Utilities */
/* ========================================================================== */
.hr { width: 100%; height: 1px; background: map-get($control-colors, border); border: 0; }
.hr-dashed { height: 1px; border-top: 1px dashed map-get($control-colors, border); background: transparent; }
/* ========================================================================== */
/* Utility Badges (aliases of pills) */
/* ========================================================================== */
.badge { @extend .pill; }
@each $name, $color in $colors { .badge-#{$name} { @extend .pill-#{$name}; } }
/* ========================================================================== */
/* Quick Utility Aliases */
/* ========================================================================== */
.rel { position: relative; }
.no-click { pointer-events: none; }
.center { display: grid; place-items: center; }
.full-height { height: 100vh; }
/* ========================================================================== */
/* Flex Grid Extensions */
/* ========================================================================== */
.row.no-gutter {
margin-inline: 0;
margin-block: 0;
> .col,
> [class^="col-"] {
padding-inline: 0;
padding-block: 0;
}
}
.row.gx-0 {
margin-right: 0;
margin-left: 0;
> .col,
> [class^="col-"] {
padding-right: 0;
padding-left: 0;
}
}
.row.gy-0 {
margin-top: 0;
margin-bottom: 0;
> .col,
> [class^="col-"] {
padding-top: 0;
padding-bottom: 0;
}
}
.row-bleed {
margin-right: calc(#{$container-padding} * -1);
margin-left: calc(#{$container-padding} * -1);
}
@each $bp, $width in $breakpoints {
@media (min-width: $width) {
.#{$bp}-row-center { justify-content: center; }
.#{$bp}-row-between { justify-content: space-between; }
.#{$bp}-row-around { justify-content: space-around; }
.#{$bp}-row-evenly { justify-content: space-evenly; }
}
}
/* ========================================================================== */
/* CSS Grid Enhancements */
/* ========================================================================== */
@for $i from 1 through 12 {
.grid-cols-#{$i} { grid-template-columns: repeat($i, 1fr) !important; }
}
.grid-cols-none { grid-template-columns: none !important; }
@for $i from 1 through 12 {
.grid-rows-#{$i} { grid-template-rows: repeat($i, 1fr) !important; }
}
.grid-rows-none { grid-template-rows: none !important; }
.grid-auto-fit-sm { grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)) !important; }
.grid-auto-fit-md { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)) !important; }
.grid-auto-fit-lg { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)) !important; }
.grid-auto-fill-sm { grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)) !important; }
.grid-auto-fill-md { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)) !important; }
.grid-auto-fill-lg { grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)) !important; }
.grid-align-center { align-items: center !important; justify-items: center !important; }
.grid-align-start { align-items: start !important; justify-items: start !important; }
.grid-align-end { align-items: end !important; justify-items: end !important; }
.grid-align-stretch { align-items: stretch !important; justify-items: stretch !important; }
.grid-flow-row { grid-auto-flow: row !important; }
.grid-flow-col { grid-auto-flow: column !important; }
.grid-flow-row-dense { grid-auto-flow: row dense !important; }
.grid-flow-col-dense { grid-auto-flow: column dense !important; }

528
rss/css/theme.css Normal file
View File

@@ -0,0 +1,528 @@
/* Grid system */
/* Breakpoints */
/* Display types */
/* Color palette */
/* Spacing scale */
/* Shadows (soft shadows) */
/* Flex utilities */
/* Button component variables */
/* Typography */
header {
display: flex;
justify-content: space-between;
padding: 0.7rem 2rem;
background-color: #ffffff;
align-items: center;
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.12);
position: relative;
z-index: 999;
font-family: "Nunito", sans-serif;
background-color: #fff;
}
header .logo {
width: 100px;
display: flex;
z-index: 999;
cursor: pointer;
background-color: #fff;
}
header .logo img {
width: 100%;
height: 100%;
}
header nav {
display: flex;
position: relative;
z-index: -1;
}
@media (max-width: 768px) {
header nav {
top: -200%;
display: flex;
flex-direction: column;
position: absolute;
opacity: 0;
transition: all 0.3s;
}
header nav.open {
top: 99%;
left: 0;
width: 100%;
transition: all 0.3s;
background-color: #fff;
height: auto;
z-index: -1;
opacity: 1;
transition: all 0.3s;
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.12);
}
header nav.open a {
padding: 7px 10px;
font-size: 16px;
}
}
header nav .menu-item {
color: #000000;
font-weight: 500;
margin-right: 15px;
display: inline-block;
position: relative;
transition: color 0.3s;
font-size: 16px;
}
header nav .menu-item.active {
font-weight: bold;
}
header nav .menu-item::after {
content: "";
position: absolute;
top: 120%;
left: 0;
height: 2px;
width: 100%;
background: #aa0b3d;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s;
}
header nav .menu-item:hover {
color: #aa0b3d;
cursor: pointer;
}
header nav .menu-item:hover::after {
transform: scaleX(1);
}
header .mobile-toggler {
background-color: #fff;
border: none;
font-size: 18px;
}
@media (min-width: 769px) {
header .mobile-toggler {
display: none;
}
}
.home-banner {
height: calc(100vh - 55px);
padding: 50px 0;
display: flex;
background-color: #ffffff;
opacity: 0;
animation: slideUp 1s ease-in-out forwards;
}
.home-banner.no-height {
height: auto !important;
}
@media (max-width: 768px) {
.home-banner {
height: 100vh;
}
}
.home-banner .home-banner-inner {
flex: 1;
display: flex;
align-items: center;
width: 100%;
}
.home-banner .home-banner-inner .container {
height: 100%;
}
.home-banner .home-banner-inner .inner-row {
display: flex;
justify-content: space-between;
align-items: stretch;
height: 100%;
}
@media (max-width: 768px) {
.home-banner .home-banner-inner .inner-row {
flex-direction: column;
}
}
.home-banner .home-banner-inner .image-col {
flex: 0 0 45%;
display: flex;
flex-direction: column;
justify-content: center;
}
.home-banner .home-banner-inner .image-col img {
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.18);
}
.home-banner .home-banner-inner .text-col {
flex: 0 0 40%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
}
@media (max-width: 768px) {
.home-banner .home-banner-inner .text-col {
flex: 0 0 100%;
max-width: 100%;
text-align: center;
z-index: 9;
}
}
.home-banner .home-banner-inner .text-col .title {
font-weight: 800;
font-size: 3.7rem;
line-height: 1.2;
font-family: "Nunito", sans-serif;
}
@media (max-width: 768px) {
.home-banner .home-banner-inner .text-col .title {
font-size: 3.2rem;
}
}
.home-banner .home-banner-inner .text-col .lead {
font-size: 1.6rem;
font-weight: 400;
}
@media (max-width: 768px) {
.home-banner .home-banner-inner .text-col .lead {
font-size: 1.3rem;
}
}
.home-banner .home-banner-inner .text-col .cta-btn {
padding: 10px;
font-size: 1.4rem;
}
.home-banner .home-banner-inner .text-col .login-text {
display: inline-flex;
gap: 5px;
font-weight: 400;
}
.home-banner .home-banner-inner .text-col .login-text .login-link {
font-weight: 500;
text-decoration: underline;
cursor: pointer;
}
.home-banner .home-banner-inner .image-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
@media (max-width: 768px) {
.home-banner .home-banner-inner .image-wrapper {
position: absolute;
top: 0;
left: 0;
z-index: 999;
height: 100%;
z-index: 1;
opacity: 0.1;
width: 100%;
}
}
.home-banner .home-banner-inner .image-wrapper img {
height: 100%;
width: 100%;
-o-object-fit: cover;
object-fit: cover;
display: block;
}
.display-banner {
width: 100%;
padding: 120px 0px;
}
.display-banner.wedding-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)), url("/rss/img/wedding.jpg");
padding: 180px 0px;
background-position: center center;
background-size: cover;
}
.display-banner.kissing-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)), url("/rss/img/couple-kissing.jpg");
padding: 180px 0px;
background-position: center center;
background-size: cover;
}
.display-banner.moving-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)), url("/rss/img/couple.jpg");
background-position: center 40%;
background-size: cover;
padding: 180px 0px;
}
footer {
background-color: #212529;
color: #fff;
display: flex;
padding: 25px 0px;
font-size: 14px;
font-weight: 300;
}
footer .logo {
width: 125px;
cursor: pointer;
}
footer ul {
list-style: none;
}
.features-section h2 {
font-size: 1.8rem;
color: #212529;
}
.features-section .theme-card {
border-radius: 0.25rem;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.features-section .theme-card .card-header {
padding: 0.5rem;
text-align: center;
}
.features-section .theme-card .card-header img {
width: 125px;
height: 125px;
border-radius: 50%;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16);
-o-object-fit: cover;
object-fit: cover;
margin: 0 auto;
}
.features-section .theme-card .card-header img.object-left {
-o-object-position: left center;
object-position: left center;
}
.features-section .theme-card .card-header img.object-center {
-o-object-position: center center;
object-position: center center;
}
.features-section .theme-card .card-header img.object-bottom {
-o-object-position: bottom center;
object-position: bottom center;
}
.features-section .theme-card .card-body {
padding: 1rem;
text-align: center;
}
.styled-title {
font-family: "Nunito", sans-serif;
}
.styled-title.fw-900 {
font-weight: 900;
}
.styled-title.fw-600 {
font-weight: 600;
}
.filter-section {
padding: 2rem 0;
}
.filter-card {
background-color: #ffffff;
border-radius: 0.25rem;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16);
padding: 1rem;
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
height: 100%;
margin: 15px 0px;
}
.filter-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #aa0b3d;
}
.filter-title {
font-size: 1.3rem;
font-weight: 500;
margin-bottom: 0.5rem;
color: #212529;
}
.filter-text {
font-size: 1rem;
line-height: 1.4;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.filter-card {
margin-bottom: 1.5rem;
}
.filter-icon {
font-size: 2rem;
}
.filter-title {
font-size: 1.1rem;
}
}
.rounded-col-image {
height: 450px;
width: 450px;
border-radius: 50%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
@media (max-width: 1200px) {
.rounded-col-image {
width: 300px;
height: 300px;
}
}
@media (max-width: 920px) {
.rounded-col-image {
width: 225px;
height: 225px;
}
}
.rounded-col-image:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
box-shadow: inset 0px 0px 27px -5px rgb(2, 2, 2);
z-index: 1;
pointer-events: none;
}
.rounded-col-image img {
-o-object-fit: cover;
object-fit: cover;
-o-object-position: right;
object-position: right;
width: 100%;
height: 100%;
}
section h2 {
font-size: 1.8rem;
font-weight: 500;
}
section h3 {
font-size: 1.6rem;
font-weight: 500;
}
section p {
font-size: 1.2rem;
font-weight: 400;
line-height: 1.4;
}
section .bolder {
font-weight: 600;
}
.section-animation {
opacity: 0;
transform: translateY(50px);
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}
.section-animation.visible {
opacity: 1;
transform: translateY(0);
}
.user-container {
width: 100%;
height: calc(100vh - 110px);
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
}
@media (max-width: 768px) {
.user-container {
height: auto;
padding: 20px 0px;
}
}
@media (max-width: 768px) {
.user-container.alt .user-card {
display: flex;
flex-direction: column-reverse;
}
}
.user-container .user-card {
border-radius: 12px;
background-color: rgba(255, 255, 255, 0.9058823529);
display: flex;
width: 100%;
flex-direction: row;
}
@media (max-width: 768px) {
.user-container .user-card {
flex-direction: column;
height: auto;
}
}
.user-container .user-card .form {
background-color: #FFF;
padding: 20px 0px;
flex: 40%;
max-width: 40%;
display: flex;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.18);
border-radius: 12px;
}
@media (max-width: 768px) {
.user-container .user-card .form {
flex: 100%;
max-width: 100%;
border-radius: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16);
}
}
.user-container .user-card .form .user-form {
padding: 10px 15px;
width: 100%;
}
.user-container .user-card .text-col {
flex: 60%;
max-width: 60%;
padding: 30px 20px;
}
@media (max-width: 768px) {
.user-container .user-card .text-col {
flex: 100%;
max-width: 100%;
}
}
.user-container .user-card .text-col .logo {
display: flex;
flex-direction: row;
justify-content: center;
}
.user-container .user-card .text-col .logo img {
width: 180px;
}
.user-container .user-card .text-col .text {
margin-top: 20px;
}
@media (prefers-reduced-motion: reduce) {
.section-animation {
opacity: 1;
transform: none;
transition: none;
}
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}/*# sourceMappingURL=theme.css.map */

1
rss/css/theme.css.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"sources":["variables.scss","theme.scss","theme.css"],"names":[],"mappings":"AAAA,gBAAA;AAIA,gBAAA;AAmBA,kBAAA;AAGA,kBAAA;AAoBA,kBAAA;AAGA,2BAAA;AAoBA,mBAAA;AAyBA,+BAAA;AAQA,eAAA;ACpGA;EACE,aAAA;EACA,8BAAA;EACA,oBAAA;EACA,yBAAA;EACA,mBAAA;EACA,+CAAA;EACA,kBAAA;EACA,YAAA;EACA,iCAAA;EACA,sBAAA;ACQF;ADFE;EACE,YAAA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EACA,sBAAA;ACIJ;ADFI;EACE,WAAA;EACA,YAAA;ACIN;ADAE;EACE,aAAA;EACA,kBAAA;EACA,WAAA;ACEJ;ADAI;EALF;IAMI,UAAA;IACA,aAAA;IACA,sBAAA;IACA,kBAAA;IACA,UAAA;IACA,oBAAA;ECGJ;EDDI;IACE,QAAA;IACA,OAAA;IACA,WAAA;IACA,oBAAA;IAEA,sBAAA;IACA,YAAA;IACA,WAAA;IACA,UAAA;IACA,oBAAA;IACA,+CAAA;ECEN;EDAM;IACE,iBAAA;IACA,eAAA;ECER;AACF;ADEI;EACE,cAAA;EACA,gBAAA;EACA,kBAAA;EACA,qBAAA;EACA,kBAAA;EACA,sBAAA;EACA,eAAA;ACAN;ADEM;EACE,iBAAA;ACAR;ADIM;EACE,WAAA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,WAAA;EACA,WAAA;EACA,mBAAA;EACA,oBAAA;EACA,sBAAA;EACA,0BAAA;ACFR;ADKM;EACE,cAAA;EACA,eAAA;ACHR;ADMM;EACE,oBAAA;ACJR;ADSE;EACE,sBAAA;EACA,YAAA;EACA,eAAA;ACPJ;ADSI;EALF;IAMI,aAAA;ECNJ;AACF;;ADUA;EACE,0BAAA;EACA,eAAA;EACA,aAAA;EACA,yBAAA;EACA,UAAA;EACA,0CAAA;ACPF;ADUE;EACE,uBAAA;ACRJ;ADWE;EAbF;IAcI,aAAA;ECRF;AACF;ADUE;EACE,OAAA;EACA,aAAA;EACA,mBAAA;EACA,WAAA;ACRJ;ADWI;EACE,YAAA;ACTN;ADYI;EACE,aAAA;EACA,8BAAA;EACA,oBAAA;EACA,YAAA;ACVN;ADYM;EANF;IAOI,sBAAA;ECTN;AACF;ADYI;EACE,aAAA;EACA,aAAA;EACA,sBAAA;EACA,uBAAA;ACVN;ADYM;EACE,mBAAA;EACA,0CAAA;ACVR;ADcI;EACE,aAAA;EACA,aAAA;EACA,sBAAA;EACA,uBAAA;EACA,SAAA;ACZN;ADcM;EAPF;IAQI,cAAA;IACA,eAAA;IACA,kBAAA;IACA,UAAA;ECXN;AACF;ADaM;EACE,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,iCD/EW;AEoEnB;ADaQ;EANF;IAOI,iBAAA;ECVR;AACF;ADaM;EACE,iBAAA;EACA,gBAAA;ACXR;ADaQ;EAJF;IAKI,iBAAA;ECVR;AACF;ADaM;EACE,aAAA;EACA,iBAAA;ACXR;ADcM;EACE,oBAAA;EACA,QAAA;EACA,gBAAA;ACZR;ADcQ;EACE,gBAAA;EACA,0BAAA;EACA,eAAA;ACZV;ADiBI;EACE,OAAA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,YAAA;ACfN;ADiBM;EAPF;IAQI,kBAAA;IACA,MAAA;IACA,OAAA;IACA,YAAA;IACA,YAAA;IACA,UAAA;IACA,YAAA;IACA,WAAA;ECdN;AACF;ADgBM;EACE,YAAA;EACA,WAAA;EACA,oBAAA;KAAA,iBAAA;EACA,cAAA;ACdR;;ADuBA;EACE,WAAA;EACA,kBAAA;ACpBF;ADsBE;EACE,sGAAA;EAEA,kBAAA;EACA,kCAAA;EACA,sBAAA;ACrBJ;ADwBE;EACE,6GAAA;EAEA,kBAAA;EACA,kCAAA;EACA,sBAAA;ACvBJ;AD0BE;EACE,qGAAA;EAEA,+BAAA;EACA,sBAAA;EACA,kBAAA;ACzBJ;;AD8BA;EACE,yBAAA;EACA,WAAA;EACA,aAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;AC3BF;AD6BE;EACE,YAAA;EACA,eAAA;AC3BJ;AD8BE;EACE,gBAAA;AC5BJ;;ADkCE;EACE,iBAAA;EACA,cAAA;AC/BJ;ADkCE;EACE,sBAAA;EACA,gBAAA;EACA,qDAAA;AChCJ;ADkCI;EACE,eAAA;EACA,kBAAA;AChCN;ADkCM;EACE,YAAA;EACA,aAAA;EACA,kBAAA;EACA,yCAAA;EACA,oBAAA;KAAA,iBAAA;EACA,cAAA;AChCR;ADkCQ;EACE,+BAAA;KAAA,4BAAA;AChCV;ADmCQ;EACE,iCAAA;KAAA,8BAAA;ACjCV;ADoCQ;EACE,iCAAA;KAAA,8BAAA;AClCV;ADuCI;EACE,aAAA;EACA,kBAAA;ACrCN;;AD4CA;EACE,iCAAA;ACzCF;AD2CE;EACE,gBAAA;ACzCJ;AD4CE;EACE,gBAAA;AC1CJ;;AD+CA;EACE,eAAA;AC5CF;;AD+CA;EACE,yBAAA;EACA,sBAAA;EACA,yCAAA;EACA,aAAA;EACA,kBAAA;EACA,qDAAA;EACA,YAAA;EACA,gBAAA;AC5CF;;AD+CA;EACE,iBAAA;EACA,mBAAA;EACA,cAAA;AC5CF;;AD+CA;EACE,iBAAA;EACA,gBAAA;EACA,qBAAA;EACA,cAAA;AC5CF;;AD+CA;EACE,eAAA;EAEA,gBAAA;AC7CF;;ADgDA,2BAAA;AACA;EACE;IACE,qBAAA;EC7CF;EDgDA;IACE,eAAA;EC9CF;EDiDA;IACE,iBAAA;EC/CF;AACF;AD2DA;EACE,aAAA;EACA,YAAA;EACA,kBAAA;EACA,gBAAA;EACA,aAAA;EACA,uBAAA;EACA,mBAAA;EACA,kBAAA;ACzDF;AD2DE;EAVF;IAWI,YAAA;IACA,aAAA;ECxDF;AACF;AD0DE;EAfF;IAgBI,YAAA;IACA,aAAA;ECvDF;AACF;ADyDE;EACE,WAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;EAGA,gDAAA;EACA,UAAA;EACA,oBAAA;ACvDJ;AD2DE;EACE,oBAAA;KAAA,iBAAA;EACA,yBAAA;KAAA,sBAAA;EACA,WAAA;EACA,YAAA;ACzDJ;;AD8DE;EACE,iBAAA;EACA,gBAAA;AC3DJ;AD8DE;EACE,iBAAA;EACA,gBAAA;AC5DJ;AD+DE;EACE,iBAAA;EACA,gBAAA;EACA,gBAAA;AC7DJ;ADgEE;EACE,gBAAA;AC9DJ;;ADkEA;EACE,UAAA;EACA,2BAAA;EACA,0DAAA;AC/DF;;ADkEA;EACE,UAAA;EACA,wBAAA;AC/DF;;ADkEA;EACE,WAAA;EACA,2BAAA;EAEA,2BAAA;EACA,4BAAA;EACA,sBAAA;EACA,aAAA;EACA,uBAAA;EACA,mBAAA;AChEF;ADkEE;EAXF;IAYI,YAAA;IACA,iBAAA;EC/DF;AACF;ADsEM;EAFF;IAGI,aAAA;IACA,8BAAA;ECnEN;AACF;ADuEE;EACE,mBAAA;EACA,mDAAA;EAEA,aAAA;EAGA,WAAA;EACA,mBAAA;ACxEJ;AD2EI;EAXF;IAYI,sBAAA;IACA,YAAA;ECxEJ;AACF;AD0EI;EACE,sBAAA;EACA,iBAAA;EAEA,SAAA;EACA,cAAA;EACA,aAAA;EACA,mBAAA;EACA,0CAAA;EACA,mBAAA;ACzEN;AD2EM;EAXF;IAYI,UAAA;IACA,eAAA;IACA,mBAAA;IACA,yCAAA;ECxEN;AACF;AD0EM;EACE,kBAAA;EACA,WAAA;ACxER;AD4EI;EACE,SAAA;EACA,cAAA;EACA,kBAAA;AC1EN;AD4EM;EALF;IAMI,UAAA;IACA,eAAA;ECzEN;AACF;AD2EM;EACE,aAAA;EACA,mBAAA;EACA,uBAAA;ACzER;AD2EQ;EACE,YAAA;ACzEV;AD6EM;EACE,gBAAA;AC3ER;;ADiFA;EACE;IACE,UAAA;IACA,eAAA;IACA,gBAAA;EC9EF;AACF;ADiFA;EACE;IACE,2BAAA;IACA,UAAA;EC/EF;EDkFA;IACE,wBAAA;IACA,UAAA;EChFF;AACF","file":"theme.css"}

604
rss/css/theme.scss Normal file
View File

@@ -0,0 +1,604 @@
@import 'variables';
header {
display: flex;
justify-content: space-between;
padding: 0.7rem 2rem;
background-color: #ffffff;
align-items: center;
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.12);
position: relative;
z-index: 999;
font-family: "Nunito", sans-serif;
background-color: #fff;
@media(max-width:768px) {
// height: 48px;
}
.logo {
width: 100px;
display: flex;
z-index: 999;
cursor: pointer;
background-color: #fff;
img {
width: 100%;
height: 100%;
}
}
nav {
display: flex;
position: relative;
z-index: -1;
@media(max-width:768px) {
top: -200%;
display: flex;
flex-direction: column;
position: absolute;
opacity: 0;
transition: all 0.3s;
&.open {
top: 99%;
left: 0;
width: 100%;
transition: all 0.3s;
// padding: 5px 10px;
background-color: #fff;
height: auto;
z-index: -1;
opacity: 1;
transition: all 0.3s;
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.12);
a {
padding: 7px 10px;
font-size: 16px;
}
}
}
.menu-item {
color: #000000;
font-weight: 500;
margin-right: 15px;
display: inline-block;
position: relative;
transition: color 0.3s;
font-size: 16px;
&.active {
font-weight: bold;
// color: #your-brand-color;
}
&::after {
content: "";
position: absolute;
top: 120%;
left: 0;
height: 2px;
width: 100%;
background: #{map-get($colors, primary)};
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s;
}
&:hover {
color: #{map-get($colors, primary)};
cursor: pointer;
}
&:hover::after {
transform: scaleX(1);
}
}
}
.mobile-toggler {
background-color: #fff;
border: none;
font-size: 18px;
@media(min-width: 769px) {
display: none;
}
}
}
.home-banner {
height: calc(100vh - 55px);
padding: 50px 0;
display: flex;
background-color: #{map-get($colors, white)};
opacity: 0;
animation: slideUp 1s ease-in-out forwards;
// animation-delay: 0.2s;
&.no-height{
height: auto !important;
}
@media(max-width:768px) {
height: 100vh;
}
.home-banner-inner {
flex: 1;
display: flex;
align-items: center;
width: 100%;
.container {
height: 100%;
}
.inner-row {
display: flex;
justify-content: space-between;
align-items: stretch;
height: 100%;
@media(max-width:768px) {
flex-direction: column;
}
}
.image-col {
flex: 0 0 45%;
display: flex;
flex-direction: column;
justify-content: center;
img {
border-radius: 12px;
box-shadow: #{map-get($shadows, 3)};
}
}
.text-col {
flex: 0 0 40%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
@media(max-width: 768px) {
flex: 0 0 100%;
max-width: 100%;
text-align: center;
z-index: 9;
}
.title {
font-weight: 800;
font-size: 3.7rem;
line-height: 1.2;
font-family: $font-family-base;
@media(max-width:768px) {
font-size: 3.2rem;
}
}
.lead {
font-size: 1.6rem;
font-weight: 400;
@media(max-width:768px) {
font-size: 1.3rem;
}
}
.cta-btn {
padding: 10px;
font-size: 1.4rem;
}
.login-text {
display: inline-flex;
gap: 5px;
font-weight: 400;
.login-link {
font-weight: 500;
text-decoration: underline;
cursor: pointer;
}
}
}
.image-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
@media(max-width: 768px) {
position: absolute;
top: 0;
left: 0;
z-index: 999;
height: 100%;
z-index: 1;
opacity: 0.1;
width: 100%;
}
img {
height: 100%;
width: 100%;
object-fit: cover;
display: block;
}
}
}
}
// Banners
.display-banner {
width: 100%;
padding: 120px 0px;
&.wedding-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)),
url('/rss/img/wedding.jpg');
padding: 180px 0px;
background-position: center center;
background-size: cover;
}
&.kissing-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)),
url('/rss/img/couple-kissing.jpg');
padding: 180px 0px;
background-position: center center;
background-size: cover;
}
&.moving-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)),
url('/rss/img/couple.jpg');
background-position: center 40%;
background-size: cover;
padding: 180px 0px;
}
}
// Footer
footer {
background-color: #{map-get($colors, 'dark')};
color: #fff;
display: flex;
padding: 25px 0px;
font-size: 14px;
font-weight: 300;
.logo {
width: 125px;
cursor: pointer;
}
ul {
list-style: none;
}
}
.features-section {
h2 {
font-size: map-get($font-sizes, xl);
color: map-get($colors, dark);
}
.theme-card {
border-radius: map-get($border-radius-sizes, md);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
.card-header {
padding: #{map-get($spacing, "2")};
text-align: center;
img {
width: 125px;
height: 125px;
border-radius: 50%;
box-shadow: map-get($shadows, 2);
object-fit: cover;
margin: 0 auto;
&.object-left {
object-position: left center;
}
&.object-center {
object-position: center center;
}
&.object-bottom {
object-position: bottom center;
}
}
}
.card-body {
padding: #{map-get($spacing, "3")};
text-align: center;
}
}
}
// Fix before release
// Theme fonts
.styled-title {
font-family: "Nunito", sans-serif;
&.fw-900 {
font-weight: 900;
}
&.fw-600 {
font-weight: 600;
}
}
.filter-section {
padding: 2rem 0;
}
.filter-card {
background-color: #{map-get($colors, white)};
border-radius: map-get($border-radius-sizes, md);
box-shadow: map-get($shadows, 2);
padding: #{map-get($spacing, "3")};
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
height: 100%;
margin: 15px 0px;
}
.filter-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #{map-get($colors, primary)};
}
.filter-title {
font-size: 1.3rem;
font-weight: 500;
margin-bottom: 0.5rem;
color: map-get($colors, dark);
}
.filter-text {
font-size: 1rem;
color: map-get($colors, gray);
line-height: 1.4;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.filter-card {
margin-bottom: 1.5rem;
}
.filter-icon {
font-size: 2rem;
}
.filter-title {
font-size: 1.1rem;
}
}
// Images
// Rounded col images
.rounded-col-image {
height: 450px;
width: 450px;
border-radius: 50%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
position: relative;
@media(max-width:1200px) {
width: 300px;
height: 300px;
}
@media(max-width:920px) {
width: 225px;
height: 225px;
}
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
-webkit-box-shadow: inset 0px 0px 27px -5px rgba(2, 2, 2, 1);
-moz-box-shadow: inset 0px 0px 27px -5px rgba(2, 2, 2, 1);
box-shadow: inset 0px 0px 27px -5px rgba(2, 2, 2, 1);
z-index: 1;
pointer-events: none;
}
img {
object-fit: cover;
object-position: right;
width: 100%;
height: 100%;
}
}
section {
h2 {
font-size: 1.8rem;
font-weight: 500;
}
h3 {
font-size: 1.6rem;
font-weight: 500;
}
p {
font-size: 1.2rem;
font-weight: 400;
line-height: 1.4;
}
.bolder {
font-weight: 600;
}
}
.section-animation {
opacity: 0;
transform: translateY(50px);
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}
.section-animation.visible {
opacity: 1;
transform: translateY(0);
}
.user-container {
width: 100%;
height: calc(100vh - 110px);
// background-image: url('/rss/img/happy-kissing.jpg');
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
@media(max-width: 768px) {
height: auto;
padding: 20px 0px;
}
&.alt {
// background-image: url('/rss/img/happy-kissing-2.jpg') !important;
.user-card {
@media(max-width: 768px) {
display: flex;
flex-direction: column-reverse;
}
}
}
.user-card {
border-radius: 12px;
background-color: #ffffffe7;
// box-shadow: #{map-get($shadows, 3)};
display: flex;
// min-height: 60vh;
// height: 60vh;
width: 100%;
flex-direction: row;
// overflow: hidden;
@media(max-width: 768px) {
flex-direction: column;
height: auto;
}
.form {
background-color: #FFF;
padding: 20px 0px;
// height: 100%;
flex: 40%;
max-width: 40%;
display: flex;
align-items: center;
box-shadow: #{map-get($shadows, 3)};
border-radius: 12px;
@media(max-width: 768px) {
flex: 100%;
max-width: 100%;
border-radius: 12px;
box-shadow: #{map-get($shadows, 2)};
}
.user-form {
padding: 10px 15px;
width: 100%;
}
}
.text-col {
flex: 60%;
max-width: 60%;
padding: 30px 20px;
@media(max-width: 768px) {
flex: 100%;
max-width: 100%;
}
.logo {
display: flex;
flex-direction: row;
justify-content: center;
img {
width: 180px;
}
}
.text {
margin-top: 20px;
}
}
}
}
@media (prefers-reduced-motion: reduce) {
.section-animation {
opacity: 1;
transform: none;
transition: none;
}
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

3
rss/css/variables.css Normal file
View File

@@ -0,0 +1,3 @@
/* ========================================================================== */
/* Flexbox Maps */
/* ========================================================================== *//*# sourceMappingURL=variables.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["variables.scss"],"names":[],"mappings":"AAwHA,+EAAA;AACA,+EAAA;AACA,+EAAA","file":"variables.css"}

348
rss/css/variables.scss Normal file
View File

@@ -0,0 +1,348 @@
$font-family-base: "Nunito", sans-serif;
$font-family-mono: Menlo, Monaco, Consolas, "Courier New", monospace;
$font-sizes: (
xs: 0.75rem,
sm: 0.875rem,
base: 1.1rem,
lg: 1.35rem,
xl: 1.8rem,
xxl: 2.2rem
);
$font-weights: (
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700
);
$line-heights: (
tight: 1,
normal: 1.3,
heading: 1.5
);
$colors: (
primary: #aa0b3d,
primary-alt: #aa0b3da8,
alt: #b5451896,
secondary: #333333,
success: #34b97b,
warning: #ffc107,
danger: #dc3545,
info: #0dcaf0,
light: #e5e5e5,
dark: #212529,
white: #ffffff,
black: #000000,
muted: #c7c7c7,
gray: #6c757d,
accent-white: #f7f7f7,
accent: #08bbbb,
transparent: transparent
);
$space: (
0: 0,
1: 0.25rem,
2: 0.5rem,
3: 1rem,
4: 1.5rem,
5: 3rem
);
$radii: (
none: 0,
sm: 0.125rem,
md: 0.25rem,
lg: 0.5rem,
xl: 1rem,
full: 9999px
);
$border-widths: (
0: 0,
1: 1px,
2: 2px,
3: 3px
);
$shadows: (
1: 0 1px 3px rgba(0, 0, 0, 0.12),
2: 0 2px 6px rgba(0, 0, 0, 0.16),
3: 0 4px 10px rgba(0, 0, 0, 0.18),
4: 0 6px 15px rgba(0, 0, 0, 0.20),
5: 0 10px 24px rgba(0, 0, 0, 0.22)
);
$drop-shadows: (
1: 0 2px 4px rgba(0, 0, 0, 0.14),
2: 0 4px 8px rgba(0, 0, 0, 0.18),
3: 0 8px 16px rgba(0, 0, 0, 0.20),
4: 0 12px 24px rgba(0, 0, 0, 0.22),
5: 0 20px 40px rgba(0, 0, 0, 0.24)
);
$timings: (
fast: 0.15s,
base: 0.25s,
slow: 0.4s
);
$easings: (
in-out: cubic-bezier(.4, 0, .2, 1),
out: cubic-bezier(0, 0, .2, 1),
in: cubic-bezier(.4, 0, 1, 1)
);
$grid-columns: 12;
$container-padding: 1rem;
$breakpoints: (
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px,
xxxl: 1600px
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1320px,
xxxl: 1520px
);
/* ========================================================================== */
/* Flexbox Maps */
/* ========================================================================== */
$flex-directions: (
row: row,
row-reverse: row-reverse,
column: column,
column-reverse: column-reverse
);
$justify-content-values: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
evenly: space-evenly
);
$align-items-values: (
start: flex-start,
end: flex-end,
center: center,
baseline: baseline,
stretch: stretch
);
$align-content-values: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
stretch: stretch
);
$gutters: (
0: map-get($space, 0),
1: map-get($space, 1),
2: map-get($space, 2),
3: map-get($space, 3),
4: map-get($space, 4),
5: map-get($space, 5)
);
$display-types: block, flex, inline, inline-block, inline-flex, grid, none;
$z: (
base: 1,
dropdown: 1000,
overlay: 1100,
modal: 1200,
toast: 1300
);
$control-colors: (
bg: #fdfdfd,
fg: map-get($colors, secondary),
border: map-get($colors, muted),
border-hover: map-get($colors, secondary),
focus: map-get($colors, primary),
ring: rgba(map-get($colors, primary), 0.2),
placeholder: map-get($colors, gray),
disabled-bg: map-get($colors, light)
);
$control-sizes: (
sm: (
font-size: map-get($font-sizes, sm),
line-height: map-get($line-heights, normal),
px: 0.5rem,
py: 0.25rem,
radius: map-get($radii, md)
),
md: (
font-size: map-get($font-sizes, base),
line-height: map-get($line-heights, normal),
px: 0.75rem,
py: 0.375rem,
radius: map-get($radii, md)
),
lg: (
font-size: map-get($font-sizes, lg),
line-height: map-get($line-heights, normal),
px: 1rem,
py: 0.5rem,
radius: map-get($radii, lg)
),
xl: (
font-size: map-get($font-sizes, xl),
line-height: map-get($line-heights, heading),
px: 1.25rem,
py: 0.75rem,
radius: map-get($radii, lg)
)
);
$control-border: (
width: map-get($border-widths, 1),
style: solid
);
$size-px: (
0: 0px,
1: 1px,
2: 2px,
4: 4px,
8: 8px,
12: 12px,
16: 16px,
20: 20px,
24: 24px,
32: 32px,
40: 40px,
48: 48px,
56: 56px,
64: 64px,
80: 80px,
96: 96px,
112: 112px,
128: 128px,
160: 160px,
192: 192px,
224: 224px,
256: 256px,
320: 320px,
384: 384px,
448: 448px,
512: 512px
);
$size-percent: (
0: 0%,
25: 25%,
33: 33.3333%,
50: 50%,
66: 66.6667%,
75: 75%,
100: 100%
);
$size-rem: (
0: 0,
1: 0.25rem,
2: 0.5rem,
3: 1rem,
4: 1.5rem,
5: 2rem,
6: 3rem,
7: 4rem,
8: 6rem
);
$size-vw: (
10: 10vw,
20: 20vw,
25: 25vw,
33: 33vw,
40: 40vw,
50: 50vw,
60: 60vw,
66: 66vw,
75: 75vw,
80: 80vw,
90: 90vw,
100: 100vw
);
$size-vh: (
10: 10vh,
20: 20vh,
25: 25vh,
33: 33vh,
40: 40vh,
50: 50vh,
60: 60vh,
66: 66vh,
75: 75vh,
80: 80vh,
90: 90vh,
100: 100vh
);
$size-dvh: (
10: 10dvh,
20: 20dvh,
25: 25dvh,
33: 33dvh,
40: 40dvh,
50: 50dvh,
60: 60dvh,
66: 66dvh,
75: 75dvh,
80: 80dvh,
90: 90dvh,
100: 100dvh
);
$size-svh: (
10: 10svh,
20: 20svh,
25: 25svh,
33: 33svh,
40: 40svh,
50: 50svh,
60: 60svh,
66: 66svh,
75: 75svh,
80: 80svh,
90: 90svh,
100: 100svh
);
$size-lvh: (
10: 10lvh,
20: 20lvh,
25: 25lvh,
33: 33lvh,
40: 40lvh,
50: 50lvh,
60: 60lvh,
66: 66lvh,
75: 75lvh,
80: 80lvh,
90: 90lvh,
100: 100lvh
);

261
rss/js/main.js Normal file
View File

@@ -0,0 +1,261 @@
// ==========================================================================
// HTTP helpers
// ==========================================================================
async function _post(formObj, target, func) {
try {
const body = await createFormData(formObj, target, func);
const res = await fetch('/rss/php/handler.php', { method: 'POST', body });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error('Post failed', err);
return { ok: false, error: String(err?.message || err) };
}
}
async function createFormData(data = {}, target, func) {
const fd = new FormData();
try {
Object.entries(data || {}).forEach(([k, v]) => {
if (Array.isArray(v)) v.forEach((item) => fd.append(`${k}[]`, item));
else if (v !== undefined && v !== null) fd.append(k, v);
});
if (target !== undefined) fd.append('target', target);
if (func !== undefined) fd.append('function', func);
} catch (err) {
console.error('Incorrect use of object', err);
}
return fd;
}
// ==========================================================================
// DOM helpers
// ==========================================================================
function createEl(tag, classArr = null, text = null, dataAttrs = null) {
const el = document.createElement(tag);
if (text !== null && text !== undefined) el.append(document.createTextNode(String(text)));
if (classArr && classArr.length) el.classList.add(...classArr);
if (dataAttrs && Object.keys(dataAttrs).length) {
for (const [k, v] of Object.entries(dataAttrs)) el.dataset[k] = v;
}
return el;
}
function getRandomID() {
if (crypto?.randomUUID) return crypto.randomUUID();
return Math.random().toString(36).slice(2);
}
// ==========================================================================
// Alerts (aligned with .alert + .alert-{name})
// ==========================================================================
/*
generateAlert('success', 'Saved!') → <div class="alert alert-success" ...>Saved!</div>
Options: { dismissAfter: ms, withClose: boolean, role: 'status'|'alert' }
*/
async function generateAlert(type = 'info', text = '', opts = {}) {
const { dismissAfter = 0, withClose = false, role = 'status' } = opts;
const alert = createEl('div', ['alert', `alert-${type}`]);
alert.setAttribute('role', role); // status (polite) or alert (assertive)
alert.setAttribute('aria-live', role === 'alert' ? 'assertive' : 'polite');
alert.append(document.createTextNode(text));
if (withClose) {
const btn = createEl('button', ['btn', 'btn-sm', 'btn-ghost-dark'], '×', { dismiss: 'alert' });
btn.setAttribute('aria-label', 'Close alert');
btn.style.marginLeft = '0.5rem';
btn.addEventListener('click', () => alert.remove());
alert.append(btn);
}
if (dismissAfter > 0) setTimeout(() => alert.remove(), dismissAfter);
return alert;
}
async function removeAlert(scope = document) {
scope.querySelectorAll('.alert').forEach((a) => a.remove());
}
// ==========================================================================
// On load
// ==========================================================================
document.addEventListener('DOMContentLoaded', () => {
// Menu toggler (pointerdown → click for better a11y; add ARIA state)
const toggler = document.getElementById('menu-toggler');
const menu = document.getElementById('mainMenu');
if (toggler && menu) {
toggler.setAttribute('aria-expanded', 'false');
toggler.setAttribute('aria-controls', 'mainMenu');
const toggleMenu = () => {
const isOpen = menu.classList.toggle('open');
toggler.setAttribute('aria-expanded', String(isOpen));
};
toggler.addEventListener('click', toggleMenu);
toggler.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleMenu(); }
});
}
// Section reveal
const sections = document.querySelectorAll('section');
const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
obs.unobserve(entry.target);
}
});
},
{ threshold: 0.05, rootMargin: '0px 0px -50px 0px' }
);
sections.forEach((section) => {
section.classList.add('section-animation');
observer.observe(section);
});
(() => {
function resolveTarget(el) {
const viaData = el.getAttribute('data-target');
const viaHref = el.getAttribute('href');
if (viaData) return document.querySelector(viaData);
if (viaHref && viaHref.startsWith('#')) return document.querySelector(viaHref);
// Fallback: if only one modal exists and no selector provided
const all = document.querySelectorAll('.modal');
return all.length === 1 ? all[0] : null;
}
function trapFocus(modal, e) {
const focusables = modal.querySelectorAll('a,button,input,textarea,select,[tabindex]:not([tabindex="-1"])');
if (!focusables.length) return;
const first = focusables[0], last = focusables[focusables.length - 1];
if (e.shiftKey && document.activeElement === first) { last.focus(); e.preventDefault(); }
else if (!e.shiftKey && document.activeElement === last) { first.focus(); e.preventDefault(); }
}
let lastActive = null;
function openModal(modal) {
if (!modal) return;
lastActive = document.activeElement;
modal.classList.add('open');
modal.setAttribute('aria-hidden', 'false');
(modal.querySelector('.modal__dialog') || modal).focus({ preventScroll: true });
modal.addEventListener('keydown', onKeyDown);
}
function closeModal(modal) {
if (!modal) return;
modal.classList.remove('open');
modal.setAttribute('aria-hidden', 'true');
modal.removeEventListener('keydown', onKeyDown);
if (lastActive) lastActive.focus({ preventScroll: true });
}
function onKeyDown(e) {
const modal = e.currentTarget;
if (e.key === 'Escape') closeModal(modal);
if (e.key === 'Tab') trapFocus(modal, e);
}
// Openers
document.querySelectorAll('.modal-open').forEach(btn => {
btn.addEventListener('click', (e) => {
const modal = resolveTarget(btn);
if (modal) openModal(modal);
e.preventDefault();
});
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); btn.click(); }
});
});
// Closers (overlay or close button)
document.addEventListener('click', (e) => {
const overlay = e.target.closest('.modal__overlay');
const closeBtn = e.target.closest('.modal__close');
if (!overlay && !closeBtn) return;
const modal = e.target.closest('.modal');
if (modal) closeModal(modal);
});
})();
(() => {
function getTargetId(tab) {
const href = tab.getAttribute('href');
if (href && href.startsWith('#')) return href.slice(1);
const aria = tab.getAttribute('aria-controls');
if (aria) return aria;
return null;
}
document.querySelectorAll('.tabs').forEach(tabs => {
const tabEls = tabs.querySelectorAll('.tab');
if (!tabEls.length) return;
// If panels are used, they should be siblings elsewhere with .tab-panel
function activateTab(tab) {
// Toggle active tab
tabEls.forEach(t => t.classList.toggle('active', t === tab));
// Panels (optional)
const targetId = getTargetId(tab);
const panels = document.querySelectorAll('.tab-panel');
if (panels.length) {
panels.forEach(p => {
const match = p.id && targetId && p.id === targetId;
p.hidden = !match;
});
}
}
// Initial state: keep existing .active or default to first
const current = tabs.querySelector('.tab.active') || tabEls[0];
if (current) activateTab(current);
tabEls.forEach(tab => {
tab.setAttribute('role', 'tab');
tab.addEventListener('click', (e) => {
activateTab(tab);
if (tab.hasAttribute('href')) e.preventDefault(); // prevent jump for href="#id"
});
tab.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); tab.click(); }
// Optional arrow key UX within the same .tabs group
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
e.preventDefault();
const arr = Array.from(tabEls);
const i = arr.indexOf(tab);
const next = e.key === 'ArrowRight' ? (i + 1) % arr.length : (i - 1 + arr.length) % arr.length;
arr[next].focus();
}
});
});
});
})();
});
document.querySelectorAll('.accordion').forEach(acc => {
const single = acc.classList.contains('accordion--single');
acc.querySelectorAll('.accordion__header').forEach(btn => {
btn.addEventListener('click', () => {
const item = btn.closest('.accordion__item');
const isOpen = item.classList.contains('is-open');
if (single) {
acc.querySelectorAll('.accordion__item.is-open').forEach(other => {
if (other !== item) other.classList.remove('is-open');
});
}
item.classList.toggle('is-open', !isOpen);
});
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); btn.click(); }
});
});
});

9
rss/json/pages/404.json Normal file
View File

@@ -0,0 +1,9 @@
{
"title": "TITLE",
"long_desc": "DESC",
"short_desc": "SHORT_DESC",
"template": "404",
"restricted": false,
"redirect_login": false,
"robots": "noindex"
}

View File

@@ -0,0 +1,8 @@
{
"title": "Listings",
"long_desc": "Testing the description!",
"short_desc": "Test desc",
"template": "listings",
"restricted": false,
"redirect_login": false
}

11
rss/php/autoload.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
spl_autoload_register(function ($class){
$class = str_replace('App\\', '', $class);
$class = str_replace('\\', '/', $class);
$file = $_SERVER['DOCUMENT_ROOT'] . '/rss/php/class/'.$class.'.php';
if(file_exists($file)){
require_once($file);
}
});

15
rss/php/class/Main.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
namespace App;
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
use PDO;
use PDOException;
use Exception;
class Main extends Sys{
public function __construct(){
}
}

34
rss/php/class/Sys.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace App;
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
use PDO;
use PDOException;
use Exception;
use stdClass;
class Sys{
protected $conn;
public function __construct(){
global $conn;
$this->conn = $conn;
}
public function validateVar($val){
if(isset($val) && !empty($val) && $val){
return true;
}else{
return false;
}
}
protected function createResponse($status, $message = null, $info = null){
$resp = new stdClass();
$resp->status = $status;
$resp->message = $message;
$resp->info = $info;
return $resp;
}
}

178
rss/php/class/Users.php Normal file
View File

@@ -0,0 +1,178 @@
<?php
namespace App;
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
use PDO;
use PDOException;
use Exception;
class Users extends Sys{
public $id;
public $fName;
public $lName;
public $email;
public $cemail;
public $psw;
public $cpsw;
private $loginState;
public function __construct(){
global $conn;
$this->conn = $conn;
}
public function login(){
if($this->isAuth()){
return $this->createResponse('success', 'Logged in');
}else{
if($this->validateVar($this->email) && $this->validateVar($this->psw)){
$stmt = $this->conn->prepare('SELECT id, psw FROM users WHERE email = :email');
$stmt->bindValue(':email', $this->email);
$stmt->execute();
if($user = $stmt->fetch(PDO::FETCH_ASSOC)){
if(password_verify($this->psw, $user['psw'])){
$auth_hash = bin2hex(random_bytes(16));
$this->id = intval($user['id']);
setcookie("user_hash", $auth_hash, [
'expires' => time() + 2678400,
'path' => '/',
// 'secure' => true, // Enable on publish
'httponly' => true,
'samesite' => 'Strict',
'domain' => $_SERVER['HTTP_HOST']
]);
setcookie("usid", $this->id, [
'expires' => time() + 2678400,
'path' => '/',
// 'secure' => true, // Enable on publish
'httponly' => false,
'samesite' => 'Strict',
'domain' => $_SERVER['HTTP_HOST']
]);
$stmt = $this->conn->prepare('INSERT INTO user_sessions (user_hash, user_id, login_ip) VALUES (:user_hash, :user_id, :login_ip)');
$stmt->bindValue(':user_hash', $auth_hash);
$stmt->bindValue(':user_id', $user['id']);
$stmt->bindValue(':login_ip', $_SERVER['REMOTE_ADDR']);
$stmt->execute();
return $this->createResponse('success', 'Logged in');
}else{
return $this->createResponse('fail', 'Incorrect details');
}
}else{
return $this->createResponse('fail', 'Incorrect details');
}
}else{
return $this->createResponse('fail', 'Please fill out all the fields');
}
}
}
public function logout(){
if(!$this->isAuth()){
return true;
}
$stmt = $this->conn->prepare('DELETE FROM user_sessions WHERE user_hash = :user_hash AND user_id = :user_id AND login_ip = :login_ip');
$stmt->bindValue(':user_hash', $_COOKIE['user_hash']);
$stmt->bindValue(':user_id', intval($_COOKIE['usid']));
$stmt->bindValue(':login_ip', $_SERVER['REMOTE_ADDR']);
$stmt->execute();
if(isset($_SERVER['HTTP_COOKIE'])) {
$cookies = explode(';', $_SERVER['HTTP_COOKIE']);
foreach ($cookies as $cookie) {
$parts = explode('=', $cookie);
$name = trim($parts[0]);
setcookie($name, '', time() - 1000);
setcookie($name, '', time() - 1000, '/');
setcookie($name, '', time() - 1000, '/', $_SERVER['HTTP_HOST']);
}
session_destroy();
}
}
public function create_user(){
// Validate inputs
if(!$this->validateVar($this->fName)){
return $this->createResponse('fail', 'Please enter your first name');
}
if(!$this->validateVar($this->lName)){
return $this->createResponse('fail', 'Please enter your last name');
}
if(!$this->validateVar($this->email)){
return $this->createResponse('fail', 'Please enter your email');
}
if($this->email != $this->cemail){
return $this->createResponse('fail', 'The email adresses do not match');
}
if(!filter_var($this->email, FILTER_VALIDATE_EMAIL)){
return $this->createResponse('fail', 'Please enter a valid email');
}
if($this->psw != $this->cpsw){
return $this->createResponse('fail', 'Please enter a valid password');
}
if(strlen($this->psw) < 6){
return $this->createResponse('fail', 'Your password needs to be at least 6 characters');
}
$stm = $this->conn->prepare('SELECT * FROM users WHERE email = :email');
$stm->bindValue(':email', $this->email);
$stm->execute();
if($stm->rowCount()){
return $this->createResponse('fail', 'Email already exists');
}
$password = password_hash(trim($this->psw), PASSWORD_BCRYPT, array('cost' => 12));
$stmt = $this->conn->prepare('INSERT INTO users (fname, lname, email, psw) VALUES (:fname, :lname, :email, :psw)');
$stmt->bindValue(':fname', trim($this->fName));
$stmt->bindValue(':lname', trim($this->lName));
$stmt->bindValue(':email', trim($this->email));
$stmt->bindValue(':psw', $password);
$stmt->execute();
if($stmt->rowCount()){
return $this->createResponse('success', 'Account has been created');
}else{
return $this->createResponse('fail', 'Something went wrong. Please try again or contact support');
}
}
public function isAuth(){
if(isset($_COOKIE['user_hash']) && !empty($_COOKIE['user_hash'])){
$user_ip = $_SERVER['REMOTE_ADDR'];
$user_hash = $_COOKIE['user_hash'];
$user_id = intval($_COOKIE['usid']);
$stmt = $this->conn->prepare('SELECT * FROM user_sessions WHERE user_hash = :user_hash AND user_id = :user_id AND login_ip = :user_ip');
$stmt->bindValue(':user_hash', $user_hash);
$stmt->bindValue(':user_id', $user_id);
$stmt->bindValue(':user_ip', $user_ip);
$stmt->execute();
return $stmt->rowCount();
}else{
return false;
}
}
public function getMyself(){
if($this->isAuth()){
$user_id = intval($_COOKIE['usid']);
$stmt = $this->conn->prepare('SELECT * FROM users WHERE id = :id');
$stmt->bindValue(':id', $user_id);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
unset($user['psw']);
return $user;
}
}
}

36
rss/php/conf.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/.env')){
$lines = file($_SERVER['DOCUMENT_ROOT'] . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if(isset($lines) && !empty($lines)){
foreach($lines as $line){
if(strpos(trim($line), '#') === 0){
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
$value = trim($value, '"\'');
putenv("$name=$value");
}
}
}
try{
$db_user = getenv("DB_USER");
$db_pass = getenv("DB_PASS");
$db_server = getenv("DB_SERVER");
$db_name = getenv("DB_NAME");
$conn = new PDO("mysql:host=$db_server;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(PDOException $e){
var_dump($e);
echo json_encode('Connection exception');
exit();
}
?>

60
rss/php/handler.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
use App\Main;
use App\Users;
$classList = array(
'Main' => Main::class,
'Users' => Users::class
);
session_start();
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php')){
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
}
if(isset($_POST['function']) && !empty($_POST['function']) && isset($_POST['target']) && !empty($_POST['target'])){
$t_class = trim($_POST['target']);
$t_func = trim($_POST['function']);
if(array_key_exists($t_class, $classList) && class_exists($classList[$t_class])){
if(method_exists($classList[$t_class], $t_func)){
$_class = new $classList[$t_class];
if(count($_POST) > 2){
foreach($_POST as $prop => $val){
if(property_exists($classList[$t_class], $prop)){
$_class->$prop = $val;
}
}
}
if(isset($_FILES) && !empty($_FILES)){
foreach($_FILES as $prop => $val){
if(property_exists($classList[$t_class], $prop)){
$_class->$prop = $val;
}
}
}
$response = $_class->$t_func();
if($response){
$msg = $response;
}else{
$msg = new stdClass();
$msg->status = 'fail';
$msg->message = $response;
}
}else{
$msg = new stdClass();
$msg->status = 'fail';
$msg->message = 'Invalid function';
}
}else{
$msg = new stdClass();
$msg->status = 'fail';
$msg->message = 'Invalid class';
}
}
die(json_encode($msg));

View File

@@ -0,0 +1,11 @@
<?php
global $pageInfo;
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/js/' . $pageInfo['template'] . '.js')) {
echo '<script src="/rss/js/' . $pageInfo['template'] . '.js"></script>';
}
?>
<footer>
</footer>
</body>
</html>

View File

@@ -0,0 +1,79 @@
<?php
$page = isset($_GET['page']) ? basename($_GET['page']) : 'index';
$jsonFile = $_SERVER['DOCUMENT_ROOT'] . "/rss/json/pages/{$page}.json";
$pageData = file_exists($jsonFile) ? json_decode(file_get_contents($jsonFile), true) : [
'title' => 'SITE',
'long_desc' => '',
'robots' => 'index, follow'
];
// Set default values
$siteUrl = 'https://DOMAIN.com';
$pageUrl = $page === 'index' ? $siteUrl . '/' : $siteUrl . '/' . $page . '/';
$title = $pageData['title'] ?? 'SITE';
$description = $pageData['long_desc'] ?? '';
$keywords = '';
$robots = $pageData['robots'] ?? 'index, follow';
$currentPage = $page === 'index' ? 'Home' : ucwords(str_replace('-', ' ', $page));
?>
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Defaults -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEBSITE | <?php echo htmlspecialchars($pageData['title'], ENT_QUOTES, 'UTF-8'); ?></title>
<meta name="description" content="<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>">
<meta name="author" content="WEBSITE">
<meta name="keywords" content="<?php echo htmlspecialchars($keywords, ENT_QUOTES, 'UTF-8'); ?>">
<meta name="robots" content="<?php echo htmlspecialchars($robots, ENT_QUOTES, 'UTF-8'); ?>">
<link rel="canonical" href="<?php echo htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8'); ?>">
<!-- Open Graph -->
<meta property="og:title" content="<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>">
<meta property="og:description" content="<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>">
<meta property="og:type" content="website">
<meta property="og:url" content="<?php echo htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8'); ?>">
<meta property="og:image" content="<?php echo $siteUrl; ?>/IMAGE">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>">
<meta name="twitter:description" content="<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>">
<meta name="twitter:image" content="<?php echo $siteUrl; ?>/IMAGE">
<!-- Favicon -->
<link rel="icon" href="/rss/img/favicon/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="/rss/img/favicon/apple-touch-icon.png">
<!-- Stylesheets -->
<link rel="preload" href="/rss/css/main.css" as="style">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/rss/css/main.css">
<link rel="stylesheet" href="/rss/css/theme.css" media="print" onload="this.media='all'">
<!-- JavaScript -->
<script src="/rss/js/main.js" defer></script>
<!-- Structured Data (JSON-LD) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>",
"url": "<?php echo htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8'); ?>",
"description": "<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>",
"isPartOf": {
"@type": "WebSite",
"name": "SITE NAME",
"url": "<?php echo $siteUrl; ?>/"
}
}
</script>
</head>
<body>
<header>
</header>

43
rss/php/pageHandler.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
session_start();
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
$page = isset($_GET['page']) && !empty($_GET['page']) ? str_replace('.php', '', $_GET['page']) : 'home';
if($page == '404'){
http_response_code(404);
}
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/json/pages/' . $page . '.json')){
// Load page configuration
$jsonInfo = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/rss/json/pages/' . $page .'.json');
$pageInfo = json_decode($jsonInfo, true);
$gU = new App\Users();
$userAuth = $gU->isAuth();
if($pageInfo['restricted'] && !$userAuth){
header('location:/login/');
}
if($pageInfo['redirect_login'] && $userAuth){
header('location:/account/');
}
}else{
http_response_code(404);
header('location:/404');
die('Not found');
}
?>
<?php
function loadContents(){
global $pageInfo;
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/php/templates/'.$pageInfo['template'].'.php')){
include($_SERVER['DOCUMENT_ROOT'] . '/rss/php/templates/'.$pageInfo['template'].'.php');
}else{
http_response_code(404);
header('location:/404');
exit();
}
}
?>

15
rss/php/templates/404.php Normal file
View File

@@ -0,0 +1,15 @@
<main>
<div class="home-banner">
<div class="home-banner-inner">
<div class="container">
<div class="d-flex justify-center align-center">
<div class="text-col">
<h1 class="title mb-3">Oops! <span class="text-primary">Page Not Found</span></h1>
<p class="lead mb-3">Looks like this page got lost in the search for love. Dont worry—lets get you back to finding your perfect match!</p>
<a href="/" class="mb-3 btn btn-dark cta-btn">Back to home</a>
</div>
</div>
</div>
</div>
</div>
</main>

696
rss/php/templates/index.php Normal file
View File

@@ -0,0 +1,696 @@
<div class="container">
<!-- ====================================================================== -->
<!-- Typography -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Typography</h2>
<p class="text-sm">Type scale, weight, alignment & transforms each example followed by its code.</p>
<!-- Example: Type scale -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Type scale</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<div class="text-xxl fw-bold lh-heading">Heading XXL</div>
<div class="text-xl fw-semibold lh-heading">Heading XL</div>
<div class="text-lg fw-medium lh-heading">Heading LG</div>
<div class="text-base">Body Base</div>
<div class="text-sm text-muted">Small muted text</div>
<div class="text-xs text-uppercase">Extra small uppercase</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="text-xxl fw-bold lh-heading"&gt;Heading XXL&lt;/div&gt;
&lt;div class="text-xl fw-semibold lh-heading"&gt;Heading XL&lt;/div&gt;
&lt;div class="text-lg fw-medium lh-heading"&gt;Heading LG&lt;/div&gt;
&lt;div class="text-base"&gt;Body Base&lt;/div&gt;
&lt;div class="text-sm text-muted"&gt;Small muted text&lt;/div&gt;
&lt;div class="text-xs text-uppercase"&gt;Extra small uppercase&lt;/div&gt;</code></pre>
</div>
<!-- Example: Alignment -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Alignment</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<div class="text-left">Left aligned</div>
<div class="text-center">Center aligned</div>
<div class="text-right">Right aligned</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="text-left"&gt;Left aligned&lt;/div&gt;
&lt;div class="text-center"&gt;Center aligned&lt;/div&gt;
&lt;div class="text-right"&gt;Right aligned&lt;/div&gt;</code></pre>
</div>
<!-- Example: Weights & transforms -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Weights &amp; transforms</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<div class="fw-light">Light</div>
<div class="fw-normal">Normal</div>
<div class="fw-semibold">Semibold</div>
<div class="fw-bold text-uppercase">Uppercase bold</div>
<div class="text-underline">Underline</div>
<div class="text-line-through">Line-through</div>
<div class="text-no-decoration">No decoration</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="fw-light"&gt;Light&lt;/div&gt;
&lt;div class="fw-normal"&gt;Normal&lt;/div&gt;
&lt;div class="fw-semibold"&gt;Semibold&lt;/div&gt;
&lt;div class="fw-bold text-uppercase"&gt;Uppercase bold&lt;/div&gt;
&lt;div class="text-underline"&gt;Underline&lt;/div&gt;
&lt;div class="text-line-through"&gt;Line-through&lt;/div&gt;
&lt;div class="text-no-decoration"&gt;No decoration&lt;/div&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Color Utilities -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Color Utilities</h2>
<p class="text-sm">Text, background, border &amp; spinner colors.</p>
<!-- Example: Text colors -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Text</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<div class="text-primary">Primary</div>
<div class="text-success">Success</div>
<div class="text-danger">Danger</div>
<div class="text-info">Info</div>
<div class="text-gray">Gray</div>
<div class="text-muted">Muted</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="text-primary"&gt;Primary&lt;/div&gt;
&lt;div class="text-success"&gt;Success&lt;/div&gt;
&lt;div class="text-danger"&gt;Danger&lt;/div&gt;
&lt;div class="text-info"&gt;Info&lt;/div&gt;
&lt;div class="text-gray"&gt;Gray&lt;/div&gt;
&lt;div class="text-muted"&gt;Muted&lt;/div&gt;</code></pre>
</div>
<!-- Example: Background colors -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Background</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2">
<div class="p-2 bg-primary text-white rounded-sm text-center">Primary</div>
<div class="p-2 bg-success text-white rounded-sm text-center">Success</div>
<div class="p-2 bg-warning rounded-sm text-center">Warning</div>
<div class="p-2 bg-danger text-white rounded-sm text-center">Danger</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="p-2 bg-primary text-white rounded-sm"&gt;Primary&lt;/div&gt;
&lt;div class="p-2 bg-success text-white rounded-sm"&gt;Success&lt;/div&gt;
&lt;div class="p-2 bg-warning rounded-sm"&gt;Warning&lt;/div&gt;
&lt;div class="p-2 bg-danger text-white rounded-sm"&gt;Danger&lt;/div&gt;</code></pre>
</div>
<!-- Example: Border colors -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Border</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<div class="p-2 border border-primary rounded-sm text-center">Primary</div>
<div class="p-2 border border-success rounded-sm text-center">Success</div>
<div class="p-2 border border-danger rounded-sm text-center">Danger</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="p-2 border border-primary rounded-sm"&gt;Primary&lt;/div&gt;
&lt;div class="p-2 border border-success rounded-sm"&gt;Success&lt;/div&gt;
&lt;div class="p-2 border border-danger rounded-sm"&gt;Danger&lt;/div&gt;</code></pre>
</div>
<!-- Example: Spinner colors -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Spinner</h3>
<div class="border rounded p-3 bg-accent-white flex items-center gap-3">
<span class="spinner spinner-primary"></span>
<span class="spinner spinner-success"></span>
<span class="spinner spinner-danger"></span>
<span class="spinner spinner-info"></span>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;span class="spinner spinner-primary"&gt;&lt;/span&gt;
&lt;span class="spinner spinner-success"&gt;&lt;/span&gt;
&lt;span class="spinner spinner-danger"&gt;&lt;/span&gt;
&lt;span class="spinner spinner-info"&gt;&lt;/span&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Spacing & Sizing -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Spacing &amp; Sizing</h2>
<p class="text-sm">Margin/padding utilities and width/height tokens.</p>
<!-- Example: Margin & Padding -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Margin &amp; Padding</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2">
<div class="bg-accent-white border rounded p-2 m-1 text-center">m-1 p-2</div>
<div class="bg-accent-white border rounded p-3 mx-2 my-1 text-center">mx-2 my-1</div>
<div class="bg-accent-white border rounded px-4 py-2 text-center">px-4 py-2</div>
<div class="bg-accent-white border rounded pt-5 pb-1 text-center">pt-5 pb-1</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="m-1 p-2"&gt;...&lt;/div&gt;
&lt;div class="mx-2 my-1 p-3"&gt;...&lt;/div&gt;
&lt;div class="px-4 py-2"&gt;...&lt;/div&gt;
&lt;div class="pt-5 pb-1"&gt;...&lt;/div&gt;</code></pre>
</div>
<!-- Example: Percentage widths -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Percentage widths</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2 items-center">
<div class="bg-info rounded text-center text-sm p-1 w-25">w-25</div>
<div class="bg-info rounded text-center text-sm p-1 w-50">w-50</div>
<div class="bg-info rounded text-center text-sm p-1 w-75">w-75</div>
<div class="bg-info rounded text-center text-sm p-1 w-100">w-100</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="w-25"&gt;...&lt;/div&gt;
&lt;div class="w-50"&gt;...&lt;/div&gt;
&lt;div class="w-75"&gt;...&lt;/div&gt;
&lt;div class="w-100"&gt;...&lt;/div&gt;</code></pre>
</div>
<!-- Example: Fixed & viewport sizes -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Fixed &amp; viewport sizes</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2 items-center">
<div class="bg-warning rounded text-center text-sm p-1 wpx-64">wpx-64</div>
<div class="bg-warning rounded text-center text-sm p-1 wrem-4">wrem-4</div>
<div class="bg-warning rounded text-center text-sm p-1 wvw-25">wvw-25</div>
<div class="bg-warning rounded text-center text-sm p-1 hvh-25">hvh-25</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="wpx-64"&gt;...&lt;/div&gt;
&lt;div class="wrem-4"&gt;...&lt;/div&gt;
&lt;div class="wvw-25"&gt;...&lt;/div&gt;
&lt;div class="hvh-25"&gt;...&lt;/div&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Layout (Containers, Flex Grid, CSS Grid) -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Layout</h2>
<p class="text-sm">Containers, flex row/cols, and CSS grid helpers.</p>
<!-- Example: Containers -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Containers</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<div class="container-narrow border rounded p-2">.container-narrow</div>
<div class="container-wide border rounded p-2">.container-wide</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="container-narrow"&gt;...&lt;/div&gt;
&lt;div class="container-wide"&gt;...&lt;/div&gt;</code></pre>
</div>
<!-- Example: Flex grid -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Flex grid (.row / .col)</h3>
<div class="border rounded p-3 bg-accent-white">
<div class="row">
<div class="col-4 p-2"><div class="border rounded p-2 text-center">col-4</div></div>
<div class="col-4 p-2"><div class="border rounded p-2 text-center">col-4</div></div>
<div class="col-4 p-2"><div class="border rounded p-2 text-center">col-4</div></div>
</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="row"&gt;
&lt;div class="col-4"&gt;...&lt;/div&gt;
&lt;div class="col-4"&gt;...&lt;/div&gt;
&lt;div class="col-4"&gt;...&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div>
<!-- Example: CSS Grid -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">CSS Grid</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<div class="border rounded p-2 text-center">1</div>
<div class="border rounded p-2 text-center">2</div>
<div class="border rounded p-2 text-center">3</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="grid grid-cols-3 gap-2"&gt;
&lt;div&gt;1&lt;/div&gt;
&lt;div&gt;2&lt;/div&gt;
&lt;div&gt;3&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Flexbox: Alignment & Justify -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2">
<h2 class="text-xl fw-semibold">Flexbox: Alignment &amp; Justify</h2>
<p class="text-sm text-muted">Use <code>.flex-*</code>, <code>.items-*</code>, and <code>.justify-*</code> to control layout.</p>
<!-- Live example -->
<div class="card p-3 stack-2">
<div class="fw-semibold">Center both axes</div>
<div class="border rounded p-3 bg-accent-white" style="height:120px;">
<div class="flex items-center justify-center h-100">
<span class="pill">.flex .items-center .justify-center</span>
</div>
</div>
<div class="fw-semibold">Space between horizontally, start vertically</div>
<div class="border rounded p-3 bg-accent-white">
<div class="flex items-start justify-between gap-2">
<span class="pill">A</span><span class="pill">B</span><span class="pill">C</span>
</div>
</div>
<div class="fw-semibold">Column + center</div>
<div class="border rounded p-3 bg-accent-white">
<div class="flex-column items-center gap-1">
<span class="pill">1</span><span class="pill">2</span><span class="pill">3</span>
</div>
</div>
</div>
<!-- Code -->
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="flex items-center justify-center"&gt;...&lt;/div&gt;
&lt;div class="flex items-start justify-between"&gt;...&lt;/div&gt;
&lt;div class="flex-column items-center"&gt;...&lt;/div&gt;</code></pre>
</section>
<!-- ====================================================================== -->
<!-- Grid: Item Placement (Span & Order) -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2">
<h2 class="text-xl fw-semibold">Grid: Item Placement</h2>
<p class="text-sm text-muted">Span columns/rows and reorder visually when needed.</p>
<div class="card p-3 stack-2">
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2">
<div class="card p-2">1</div>
<div class="card p-2" style="grid-column: span 2;">2 spans 2 cols</div>
<div class="card p-2">3</div>
<div class="card p-2" style="grid-column: 1 / -1;">4 full row</div>
</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="grid grid-cols-4"&gt;
&lt;div&gt;1&lt;/div&gt;
&lt;div style="grid-column: span 2;"&gt;2&lt;/div&gt;
&lt;div&gt;3&lt;/div&gt;
&lt;div style="grid-column: 1 / -1;"&gt;4&lt;/div&gt;
&lt;/div&gt;</code></pre>
</section>
<!-- ====================================================================== -->
<!-- Responsive Flex vs Grid (When to use which) -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2">
<h2 class="text-xl fw-semibold">Flex vs Grid (Quick Patterns)</h2>
<p class="text-sm text-muted">Use flex for 1-dimensional alignment; use grid for true 2-D layouts.</p>
<div class="grid grid-cols-2 gap-3">
<div class="card p-3 stack-2">
<div class="fw-semibold">Nav bar (Flex)</div>
<div class="border rounded p-2 bg-accent-white flex items-center justify-between">
<span class="pill">Logo</span>
<div class="flex gap-1">
<span class="pill">Link</span><span class="pill">Link</span><span class="pill">Link</span>
</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="flex items-center justify-between"&gt;...&lt;/div&gt;</code></pre>
</div>
<div class="card p-3 stack-2">
<div class="fw-semibold">Card gallery (Grid)</div>
<div class="border rounded p-2 bg-accent-white grid grid-auto-fit-md gap-2">
<article class="card p-2">Card</article>
<article class="card p-2">Card</article>
<article class="card p-2">Card</article>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="grid grid-auto-fit-md gap-2"&gt;...&lt;/div&gt;</code></pre>
</div>
</div>
</section>
<!-- ====================================================================== -->
<!-- Borders, Radius, Shadows -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Borders, Radius, Shadows</h2>
<p class="text-sm">Border widths, corner radii, and shadow levels.</p>
<!-- Example: Borders & Radius -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Borders &amp; radius</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<div class="border rounded-sm p-2 text-center">.border .rounded-sm</div>
<div class="border-2 rounded-lg p-2 text-center">.border-2 .rounded-lg</div>
<div class="border-3 rounded-xl p-2 text-center">.border-3 .rounded-xl</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="border rounded-sm"&gt;...&lt;/div&gt;
&lt;div class="border-2 rounded-lg"&gt;...&lt;/div&gt;
&lt;div class="border-3 rounded-xl"&gt;...&lt;/div&gt;</code></pre>
</div>
<!-- Example: Card shadows -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Card shadows</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<article class="card p-2">.card</article>
<article class="card card-shadow-4 p-2">.card .card-shadow-4</article>
<article class="card shadow-none p-2">.card .shadow-none</article>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;article class="card"&gt;...&lt;/article&gt;
&lt;article class="card card-shadow-4"&gt;...&lt;/article&gt;
&lt;article class="card shadow-none"&gt;...&lt;/article&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Controls (Inputs & Buttons) -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Controls</h2>
<p class="text-sm">Shared blueprint, sizes, and variants.</p>
<!-- Example: Inputs -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Inputs</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<input class="input input-sm" placeholder="Small" />
<input class="input input-md" placeholder="Medium" />
<input class="input input-lg" placeholder="Large" />
<input class="input input-xl" placeholder="XL" />
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;input class="input input-sm" placeholder="Small" /&gt;
&lt;input class="input input-md" placeholder="Medium" /&gt;
&lt;input class="input input-lg" placeholder="Large" /&gt;
&lt;input class="input input-xl" placeholder="XL" /&gt;</code></pre>
</div>
<!-- Example: Buttons -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Buttons</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<button class="btn btn-sm btn-primary">Primary</button>
<button class="btn btn-md btn-success">Success</button>
<button class="btn btn-lg btn-outline-danger">Outline Danger</button>
<button class="btn btn-xl btn-ghost-info">Ghost Info</button>
<button class="btn btn-md" disabled>Disabled</button>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;button class="btn btn-sm btn-primary"&gt;Primary&lt;/button&gt;
&lt;button class="btn btn-md btn-success"&gt;Success&lt;/button&gt;
&lt;button class="btn btn-lg btn-outline-danger"&gt;Outline Danger&lt;/button&gt;
&lt;button class="btn btn-xl btn-ghost-info"&gt;Ghost Info&lt;/button&gt;
&lt;button class="btn btn-md" disabled&gt;Disabled&lt;/button&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Alerts -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Alerts</h2>
<p class="text-sm">Contextual feedback banners.</p>
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Variants</h3>
<div class="border rounded p-3 bg-accent-white stack-1">
<div class="alert">Base alert</div>
<div class="alert alert-primary">Primary alert</div>
<div class="alert alert-success">Success alert</div>
<div class="alert alert-warning">Warning alert</div>
<div class="alert alert-danger">Danger alert</div>
<div class="alert alert-info">Info alert</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="alert"&gt;Base alert&lt;/div&gt;
&lt;div class="alert alert-primary"&gt;Primary alert&lt;/div&gt;
&lt;div class="alert alert-success"&gt;Success alert&lt;/div&gt;
&lt;div class="alert alert-warning"&gt;Warning alert&lt;/div&gt;
&lt;div class="alert alert-danger"&gt;Danger alert&lt;/div&gt;
&lt;div class="alert alert-info"&gt;Info alert&lt;/div&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Cards -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Cards</h2>
<p class="text-sm">Surface container with border/shadow variants.</p>
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Examples</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<article class="card p-2">Default card</article>
<article class="card card-border-primary p-2">Bordered (primary)</article>
<article class="card card-shadow-4 p-2">Shadow level 4</article>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;article class="card"&gt;...&lt;/article&gt;
&lt;article class="card card-border-primary"&gt;...&lt;/article&gt;
&lt;article class="card card-shadow-4"&gt;...&lt;/article&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- Tabs & Tooltips -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Tabs (With Panels)</h2>
<p class="text-sm text-muted">
Each tab points to a content panel by <code>href="#panel-id"</code>.
The JS shows the matching panel and hides the others.
</p>
<!-- Live example -->
<div class="card p-3 stack-2">
<div class="tabs">
<a class="tab active" href="#tab-panel-1">One</a>
<a class="tab" href="#tab-panel-2">Two</a>
<a class="tab" href="#tab-panel-3">Three</a>
</div>
<div class="stack-2 mt-2">
<div class="tab-panel card p-2" id="tab-panel-1">Content for tab one.</div>
<div class="tab-panel card p-2" id="tab-panel-2" hidden>Content for tab two.</div>
<div class="tab-panel card p-2" id="tab-panel-3" hidden>Content for tab three.</div>
</div>
</div>
<!-- Copy-paste code -->
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="tabs"&gt;
&lt;a class="tab active" href="#tab-panel-1"&gt;One&lt;/a&gt;
&lt;a class="tab" href="#tab-panel-2"&gt;Two&lt;/a&gt;
&lt;a class="tab" href="#tab-panel-3"&gt;Three&lt;/a&gt;
&lt;/div&gt;
&lt;div class="stack-2 mt-2"&gt;
&lt;div class="tab-panel card p-2" id="tab-panel-1"&gt;Content for tab one.&lt;/div&gt;
&lt;div class="tab-panel card p-2" id="tab-panel-2" hidden&gt;Content for tab two.&lt;/div&gt;
&lt;div class="tab-panel card p-2" id="tab-panel-3" hidden&gt;Content for tab three.&lt;/div&gt;
&lt;/div&gt;</code></pre>
<ul class="text-sm text-muted stack-1">
<li>Make sure each tabs <code>href</code> matches a panels <code>id</code>.</li>
<li>Only one panel should be visible the others have <code>hidden</code>.</li>
<li>Keep classes exactly as shown: <code>.tabs</code>, <code>.tab</code>, <code>.tab-panel</code>.</li>
</ul>
</section>
<!-- ====================================================================== -->
<!-- Modal -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Modal</h2>
<p class="text-sm text-muted">
Click the button to open a modal. Close it by clicking the overlay or the button.
Works with the class-based JS you added (looks for <code>.modal-open</code>, <code>.modal__overlay</code>, <code>.modal__close</code>).
</p>
<!-- Live example -->
<div class="card p-3 stack-2">
<button class="btn btn-md btn-primary modal-open" href="#demoModal">Open demo modal</button>
<div class="modal" id="demoModal" aria-hidden="true" role="dialog" aria-modal="true">
<div class="modal__overlay"></div>
<div class="modal__dialog stack-2" role="document">
<div class="modal__header">
<h3 class="modal__title">Demo modal</h3>
<button class="modal__close" aria-label="Close"></button>
</div>
<div class="stack-2">
<p>Put your content here.</p>
<div class="flex items-center gap-2">
<input class="input input-md" placeholder="Your name" />
<button class="btn btn-md btn-success">Save</button>
</div>
</div>
</div>
</div>
</div>
<!-- Copy-paste code -->
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;button class="btn btn-md btn-primary modal-open" href="#demoModal"&gt;Open demo modal&lt;/button&gt;
&lt;div class="modal" id="demoModal" aria-hidden="true" role="dialog" aria-modal="true"&gt;
&lt;div class="modal__overlay"&gt;&lt;/div&gt;
&lt;div class="modal__dialog" role="document"&gt;
&lt;div class="modal__header"&gt;
&lt;h3 class="modal__title"&gt;Demo modal&lt;/h3&gt;
&lt;button class="modal__close" aria-label="Close"&gt;&lt;/button&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;Put your content here.&lt;/p&gt;
&lt;div class="flex items-center gap-2"&gt;
&lt;input class="input input-md" placeholder="Your name" /&gt;
&lt;button class="btn btn-md btn-success"&gt;Save&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre>
<!-- Tips -->
<ul class="text-sm text-muted stack-1">
<li>Trigger: <code>.modal-open</code> on a button/link. Use <code>href="#modalId"</code> or <code>data-target="#modalId"</code>.</li>
<li>Close: add <code>.modal__close</code> to a button or click the <code>.modal__overlay</code>.</li>
<li>Only change the <code>id</code> and the content keep the classes the same.</li>
</ul>
</section>
<!-- ====================================================================== -->
<!-- Utilities -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">Utilities</h2>
<p class="text-sm">Motion, overflow, ratio, cursors &amp; opacity.</p>
<!-- Example: Motion & text -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Motion &amp; text</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<div class="p-2 border rounded hover-raise text-center">.hover-raise</div>
<div class="p-2 border rounded hover-glow text-center">.hover-glow</div>
<div class="p-2 border rounded truncate w-50">This is a very long line that will be truncated.</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="hover-raise"&gt;...&lt;/div&gt;
&lt;div class="hover-glow"&gt;...&lt;/div&gt;
&lt;div class="truncate w-50"&gt;Long long text...&lt;/div&gt;</code></pre>
</div>
<!-- Example: Effects, overflow, ratios -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">Effects, overflow, ratios</h3>
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
<div class="p-2 border rounded opacity-50 text-center">.opacity-50</div>
<div class="p-2 border rounded cursor-not-allowed text-center">.cursor-not-allowed</div>
<div class="p-2 border rounded overflow-scroll" style="max-height:3rem">
.overflow-scroll<br/>Line 2<br/>Line 3<br/>Line 4
</div>
<div class="p-2 border rounded ratio ratio-16x9"><img class="object-cover" src="https://picsum.photos/640/360" alt=""></div>
<div class="p-2 border rounded ratio ratio-1x1"><img class="object-contain" src="https://picsum.photos/512" alt=""></div>
<div class="p-2 border rounded blur-sm text-center">.blur-sm</div>
</div>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>&lt;div class="opacity-50"&gt;...&lt;/div&gt;
&lt;div class="cursor-not-allowed"&gt;...&lt;/div&gt;
&lt;div class="overflow-scroll"&gt;...&lt;/div&gt;
&lt;div class="ratio ratio-16x9"&gt;&lt;img class="object-cover" ... /&gt;&lt;/div&gt;
&lt;div class="ratio ratio-1x1"&gt;&lt;img class="object-contain" ... /&gt;&lt;/div&gt;
&lt;div class="blur-sm"&gt;...&lt;/div&gt;</code></pre>
</div>
</section>
<!-- ====================================================================== -->
<!-- JavaScript Helpers (Reference) -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h2 class="text-xl fw-semibold">JavaScript Helpers (Reference)</h2>
<p class="text-sm">
Lightweight utilities used across the docs and components. Keep logic simple and colocated.
This card describes the functions and expected usage; copy snippets as needed.
</p>
<!-- HTTP -->
<div class="card p-3 stack-2">
<h3 class="text-lg fw-medium">HTTP</h3>
<p class="text-sm">Post JSON-ish data via <code>FormData</code> to the PHP handler.</p>
<div class="stack-1">
<div>
<div class="fw-semibold">_post(formObj, target, func)</div>
<p class="text-sm text-gray">Builds a <code>FormData</code> payload and POSTs to <code>/rss/php/handler.php</code>, returning JSON.</p>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
const res = await _post(
{ name: 'Ada', roles: ['admin','editor'] },
'users',
'create'
);
// res → parsed JSON (or { ok:false, error:"..." } on failure)</code></pre>
</div>
<div>
<div class="fw-semibold">createFormData(data, target, func)</div>
<p class="text-sm text-gray">Converts plain objects/arrays into <code>FormData</code> and appends <code>target</code>/<code>function</code>.</p>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
const fd = await createFormData({ tags: ['a','b'], avatar: file }, 'profile', 'update');</code></pre>
</div>
</div>
</div>
<!-- ====================================================================== -->
<!-- DOM -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h3 class="text-lg fw-medium">DOM</h3>
<p class="text-sm text-muted">Small helpers to create elements and IDs.</p>
<div class="stack-2">
<div class="card p-3 stack-1">
<div class="fw-semibold">createEl(tag, classArr?, text?, dataAttrs?)</div>
<p class="text-sm text-gray">Create an element, add classes, text, and <code>data-*</code> attributes.</p>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
const btn = createEl('button', ['btn','btn-md','btn-primary'], 'Save', { action: 'save' });
document.body.append(btn);</code></pre>
</div>
<div class="card p-3 stack-1">
<div class="fw-semibold">getRandomID()</div>
<p class="text-sm text-gray">Generates a unique id (uses <code>crypto.randomUUID()</code> if available).</p>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
const id = getRandomID(); // "c1c0f33a-..." or base36 fallback</code></pre>
</div>
</div>
</section>
<!-- ====================================================================== -->
<!-- Alerts -->
<!-- ====================================================================== -->
<section class="card p-3 stack-2 my-2">
<h3 class="text-lg fw-medium">Alerts</h3>
<p class="text-sm text-muted">Create and remove framework-styled alerts using <code>.alert</code> and <code>.alert-{color}</code>.</p>
<div class="card p-3 stack-2">
<div class="fw-semibold">generateAlert(type, text, options?) HTMLElement</div>
<p class="text-sm text-gray">
Types align with your color tokens: <code>primary</code>, <code>success</code>, <code>warning</code>, <code>danger</code>, <code>info</code>, etc.
</p>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Create and insert
const alertEl = await generateAlert('warning', 'Heads up!', { withClose: true, dismissAfter: 4000 });
document.querySelector('#alertHost').append(alertEl);
// Common types
await generateAlert('success', 'Saved!');
await generateAlert('danger', 'Something went wrong', { role: 'alert' });</code></pre>
<div class="fw-semibold">removeAlert(scope?)</div>
<p class="text-sm text-gray">Remove all alerts in <code>scope</code> (default: <code>document</code>).</p>
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
removeAlert(); // or removeAlert(document.querySelector('#alertHost'));</code></pre>
</div>
</section>

8
sitemap.xml Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://DOMAIN.com/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>