chg: usr: improve the gfx on homepage - implement login/register and activation for authentication - and add the first version of profile page #4
9
assets/css/homepage/_animations.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
@keyframes appear {
|
||||
from { opacity: 0; transform: scale(0.94); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
from { opacity: 0; transform: translateY(16px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
80
assets/css/homepage/_auth-bar.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
.hero-auth {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
right: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hero-auth-user {
|
||||
font: 600 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.75);
|
||||
letter-spacing: 0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i { font-size: 15px; }
|
||||
}
|
||||
|
||||
.hero-auth-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font: 600 12px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
text-decoration: none;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 4px;
|
||||
padding: 7px 14px;
|
||||
cursor: pointer;
|
||||
transition: all 200ms ease;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
&--register {
|
||||
color: rgba(149, 207, 245, 0.8);
|
||||
border-color: rgba(35, 111, 135, 0.4);
|
||||
background: rgba(35, 111, 135, 0.12);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgba(35, 111, 135, 0.28);
|
||||
border-color: rgba(149, 207, 245, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&--out {
|
||||
background: transparent;
|
||||
border-color: rgba(173, 10, 5, 0.3);
|
||||
color: rgba(246, 125, 82, 0.7);
|
||||
|
||||
&:hover {
|
||||
background: rgba(173, 10, 5, 0.15);
|
||||
border-color: rgba(246, 125, 82, 0.5);
|
||||
color: #f67d52;
|
||||
}
|
||||
}
|
||||
|
||||
&--profile {
|
||||
color: rgba(149, 207, 245, 0.8);
|
||||
border-color: rgba(35, 111, 135, 0.35);
|
||||
background: rgba(35, 111, 135, 0.08);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgba(35, 111, 135, 0.22);
|
||||
border-color: rgba(149, 207, 245, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
219
assets/css/homepage/_auth.scss
Normal file
@@ -0,0 +1,219 @@
|
||||
.auth-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px 80px;
|
||||
}
|
||||
|
||||
.auth-flash {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 12px 18px;
|
||||
border-radius: 5px;
|
||||
font: 600 14px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&--success {
|
||||
background: rgba(26, 104, 68, 0.25);
|
||||
border: 1px solid rgba(42, 158, 96, 0.4);
|
||||
color: #a0f0c0;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background: rgba(173, 10, 5, 0.18);
|
||||
border: 1px solid rgba(173, 10, 5, 0.4);
|
||||
color: #f6a090;
|
||||
}
|
||||
}
|
||||
|
||||
// "Check your inbox" confirmation card
|
||||
.auth-card--sent {
|
||||
text-align: center;
|
||||
padding: 48px 40px;
|
||||
}
|
||||
|
||||
.auth-sent-icon {
|
||||
font-size: 52px;
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
margin-bottom: 20px;
|
||||
filter: drop-shadow(0 0 16px rgba(35, 111, 135, 0.4));
|
||||
}
|
||||
|
||||
.auth-sent-email {
|
||||
font: 700 16px 'Rajdhani', sans-serif;
|
||||
color: #95cff5;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 0 0 20px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.auth-sent-note {
|
||||
font: 400 14px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 0;
|
||||
|
||||
strong { color: rgba(255, 255, 255, 0.75); }
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(35, 111, 135, 0.2);
|
||||
border-radius: 10px;
|
||||
padding: 44px 48px 40px;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font: 800 30px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.auth-sub {
|
||||
font: 400 14px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.auth-error {
|
||||
background: rgba(173, 10, 5, 0.18);
|
||||
border: 1px solid rgba(173, 10, 5, 0.4);
|
||||
border-radius: 5px;
|
||||
padding: 10px 14px;
|
||||
font: 600 13px 'Rajdhani', sans-serif;
|
||||
color: #f6a090;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.auth-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.auth-label {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.auth-input-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.auth-input-icon {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
color: rgba(149, 207, 245, 0.4);
|
||||
font-size: 13px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.auth-input {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(35, 111, 135, 0.3);
|
||||
border-radius: 5px;
|
||||
padding: 11px 14px 11px 34px;
|
||||
font: 500 15px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
letter-spacing: 0.5px;
|
||||
transition: border-color 200ms ease, background 200ms ease;
|
||||
|
||||
&::placeholder { color: rgba(255, 255, 255, 0.2); }
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: rgba(35, 111, 135, 0.1);
|
||||
border-color: rgba(149, 207, 245, 0.5);
|
||||
}
|
||||
|
||||
&--error {
|
||||
border-color: rgba(173, 10, 5, 0.6) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-field-error {
|
||||
font: 500 12px 'Rajdhani', sans-serif;
|
||||
color: #f6a090;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.auth-remember {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font: 500 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin-top: -4px;
|
||||
|
||||
input[type="checkbox"] { accent-color: #236f87; }
|
||||
}
|
||||
|
||||
.auth-submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
background: linear-gradient(to bottom, #ad0a05 0%, #d4401a 55%, #f67d52 100%);
|
||||
border: 1px solid rgba(246, 125, 82, 0.3);
|
||||
border-radius: 5px;
|
||||
color: #ffffff;
|
||||
font: 700 16px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
padding: 14px;
|
||||
cursor: pointer;
|
||||
margin-top: 6px;
|
||||
box-shadow: 0 4px 20px rgba(173, 10, 5, 0.35);
|
||||
transition: all 220ms ease;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #c91008 0%, #e5521e 55%, #ff8c61 100%);
|
||||
box-shadow: 0 6px 28px rgba(173, 10, 5, 0.6);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:active { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.auth-switch {
|
||||
font: 400 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
letter-spacing: 0.3px;
|
||||
|
||||
a {
|
||||
color: #95cff5;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: color 180ms;
|
||||
|
||||
&:hover { color: #c5e8ff; }
|
||||
}
|
||||
}
|
||||
35
assets/css/homepage/_content.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
main div.txt {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 60px 40px 80px;
|
||||
}
|
||||
|
||||
main div.txt h2 {
|
||||
font: bold 28px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
margin-bottom: 30px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
main div.txt h3 {
|
||||
font: bold 17px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
margin: 28px 0 10px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
main div.txt p,
|
||||
main div.txt li {
|
||||
font: 400 15px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
main div.txt a {
|
||||
color: #95cff5;
|
||||
text-decoration: none;
|
||||
transition: color 180ms;
|
||||
|
||||
&:hover { color: #c5e8ff; }
|
||||
}
|
||||
75
assets/css/homepage/_cta.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
.hero-cta {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font: 800 28px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 6px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
padding: 22px 100px 20px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(246, 125, 82, 0.25);
|
||||
|
||||
background: linear-gradient(to bottom, #b30c06 0%, #d63d15 50%, #f67d52 100%);
|
||||
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(173, 10, 5, 0.2),
|
||||
0 0 30px rgba(173, 10, 5, 0.35),
|
||||
0 6px 24px rgba(0, 0, 0, 0.5),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
||||
|
||||
transition: transform 220ms ease, box-shadow 220ms ease, letter-spacing 220ms ease;
|
||||
animation: rise 0.8s 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
// Outer glow layer (blurred duplicate, always visible)
|
||||
.hero-cta::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(to bottom, #ad0a05, #f67d52);
|
||||
filter: blur(18px);
|
||||
opacity: 0.3;
|
||||
z-index: -1;
|
||||
transition: opacity 220ms ease;
|
||||
}
|
||||
|
||||
.hero-cta:hover {
|
||||
transform: translateY(-4px);
|
||||
letter-spacing: 8px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(246, 125, 82, 0.3),
|
||||
0 0 50px rgba(173, 10, 5, 0.65),
|
||||
0 10px 32px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.hero-cta:hover::before {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.hero-cta:active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// Version / copyright line
|
||||
.hero-meta {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font: 400 12px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 58px;
|
||||
animation: rise 0.8s 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.hero-meta a {
|
||||
color: rgba(149, 207, 245, 0.65);
|
||||
text-decoration: none;
|
||||
transition: color 180ms;
|
||||
}
|
||||
|
||||
.hero-meta a:hover {
|
||||
color: #95cff5;
|
||||
}
|
||||
202
assets/css/homepage/_features.scss
Normal file
@@ -0,0 +1,202 @@
|
||||
.feature-block {
|
||||
width: 100%;
|
||||
padding: 80px 40px;
|
||||
position: relative;
|
||||
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(35, 111, 135, 0.12);
|
||||
}
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.feature-block__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.feature-block--reverse .feature-block__inner {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
// Visual side
|
||||
.feature-block__visual {
|
||||
flex: 0 0 340px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Stats icons cluster
|
||||
.feature-block__visual--stats {
|
||||
height: 260px;
|
||||
gap: 0;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
color: rgba(35, 111, 135, 0.5);
|
||||
transition: color 300ms ease;
|
||||
}
|
||||
|
||||
// Bar chart — large, centre
|
||||
i.fa-bar-chart {
|
||||
font-size: 140px;
|
||||
color: rgba(35, 111, 135, 0.35);
|
||||
filter: drop-shadow(0 0 30px rgba(35, 111, 135, 0.3));
|
||||
}
|
||||
|
||||
// Trophy — top right
|
||||
i.fa-trophy {
|
||||
font-size: 64px;
|
||||
top: 12px;
|
||||
right: 30px;
|
||||
color: rgba(246, 125, 82, 0.5);
|
||||
filter: drop-shadow(0 0 16px rgba(246, 125, 82, 0.25));
|
||||
}
|
||||
|
||||
// History — bottom left
|
||||
i.fa-history {
|
||||
font-size: 52px;
|
||||
bottom: 18px;
|
||||
left: 30px;
|
||||
color: rgba(149, 207, 245, 0.4);
|
||||
filter: drop-shadow(0 0 12px rgba(149, 207, 245, 0.2));
|
||||
}
|
||||
|
||||
&:hover i.fa-bar-chart { color: rgba(35, 111, 135, 0.6); }
|
||||
&:hover i.fa-trophy { color: rgba(246, 125, 82, 0.75); }
|
||||
&:hover i.fa-history { color: rgba(149, 207, 245, 0.65); }
|
||||
}
|
||||
|
||||
// MSN visual
|
||||
.feature-block__visual--msn {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.msn-logo {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 18px rgba(149, 207, 245, 0.3)) brightness(1.1);
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.msn-screenshot {
|
||||
width: 340px;
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 8px 40px rgba(0, 0, 0, 0.6),
|
||||
0 0 0 1px rgba(35, 111, 135, 0.12);
|
||||
filter: saturate(0.85) brightness(0.9);
|
||||
transition: filter 300ms ease;
|
||||
|
||||
&:hover {
|
||||
filter: saturate(1) brightness(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Text side
|
||||
.feature-block__text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.feature-block__label {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
color: rgba(149, 207, 245, 0.55);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-block__title {
|
||||
font: 800 40px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.feature-block__body {
|
||||
font: 400 16px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
line-height: 1.8;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.feature-block__cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 28px;
|
||||
font: 700 14px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
text-decoration: none;
|
||||
color: rgba(149, 207, 245, 0.85);
|
||||
border: 1px solid rgba(35, 111, 135, 0.4);
|
||||
background: rgba(35, 111, 135, 0.1);
|
||||
padding: 11px 24px;
|
||||
border-radius: 4px;
|
||||
transition: all 200ms ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(35, 111, 135, 0.25);
|
||||
border-color: rgba(149, 207, 245, 0.55);
|
||||
color: #fff;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(35, 111, 135, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.feature-block__inner,
|
||||
.feature-block--reverse .feature-block__inner {
|
||||
flex-direction: column;
|
||||
gap: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-block__visual {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.feature-block__visual--stats {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.feature-block__visual--msn {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.msn-screenshot {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.feature-block__label,
|
||||
.feature-block__cta {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.feature-block__title { font-size: 32px; }
|
||||
|
||||
.feature-block {
|
||||
padding: 60px 24px;
|
||||
}
|
||||
}
|
||||
115
assets/css/homepage/_footer.scss
Normal file
@@ -0,0 +1,115 @@
|
||||
footer {
|
||||
background: #040608;
|
||||
border-top: 1px solid rgba(35, 111, 135, 0.12);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 60px 60px 52px;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
// Left: brand block
|
||||
.footer-brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
opacity: 0.55;
|
||||
filter:
|
||||
drop-shadow(0 0 12px rgba(35, 111, 135, 0.4))
|
||||
brightness(1.1);
|
||||
transition: opacity 250ms ease, filter 250ms ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
filter:
|
||||
drop-shadow(0 0 20px rgba(35, 111, 135, 0.65))
|
||||
brightness(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.footer-name {
|
||||
font: 700 22px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
font: 400 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.7);
|
||||
letter-spacing: 0.5px;
|
||||
max-width: 240px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// Right: navigation
|
||||
.footer-nav-label {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.footer-nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.footer-nav ul li a {
|
||||
display: block;
|
||||
font: 500 15px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
white-space: nowrap;
|
||||
padding: 6px 0;
|
||||
transition: color 180ms ease, letter-spacing 180ms ease;
|
||||
|
||||
&:hover {
|
||||
color: #95cff5;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom copyright bar
|
||||
.footer-copy {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding: 16px 60px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
|
||||
p {
|
||||
font: 400 11px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
text-decoration: none;
|
||||
transition: color 180ms;
|
||||
|
||||
&:hover { color: #95cff5; }
|
||||
}
|
||||
}
|
||||
39
assets/css/homepage/_header.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
// Minesweeper grid texture
|
||||
background-color: #07090d;
|
||||
background-image:
|
||||
linear-gradient(rgba(35, 111, 135, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.1) 1px, transparent 1px);
|
||||
background-size: 46px 46px;
|
||||
}
|
||||
|
||||
// Deep radial vignette – grid fades toward the centre
|
||||
header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(
|
||||
ellipse 85% 75% at 50% 50%,
|
||||
#07090d 10%,
|
||||
transparent 75%
|
||||
);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Smoke at the bottom so header bleeds into body
|
||||
header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background: linear-gradient(to bottom, transparent, #07090d);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
49
assets/css/homepage/_hero-compact.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
.hero--compact {
|
||||
min-height: unset;
|
||||
padding: 36px 60px 48px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: left;
|
||||
gap: 52px;
|
||||
|
||||
&::before, &::after { display: none; }
|
||||
|
||||
.hero-logo {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
img { width: 180px; }
|
||||
}
|
||||
|
||||
.hero-body {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 12px 52px 10px;
|
||||
font-size: 18px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// Also shrink the bottom fade on sub-pages
|
||||
header:has(.hero--compact)::after {
|
||||
height: 60px;
|
||||
}
|
||||
90
assets/css/homepage/_hero.scss
Normal file
@@ -0,0 +1,90 @@
|
||||
.hero {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
padding: 80px 40px 160px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
// Decorative glow blobs in opposite corners
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
left: -60px;
|
||||
width: 420px;
|
||||
height: 420px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(173, 10, 5, 0.09) 0%, transparent 65%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
right: -60px;
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(35, 111, 135, 0.1) 0%, transparent 65%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
// Logo
|
||||
.hero-logo {
|
||||
display: block;
|
||||
margin-bottom: 72px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
animation: appear 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.hero-logo img {
|
||||
width: 400px;
|
||||
max-width: 82vw;
|
||||
filter:
|
||||
drop-shadow(0 0 40px rgba(35, 111, 135, 0.35))
|
||||
drop-shadow(0 6px 20px rgba(0, 0, 0, 0.8));
|
||||
}
|
||||
|
||||
// Body text block
|
||||
.hero-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font: 300 17px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
letter-spacing: 3px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 18px;
|
||||
animation: rise 0.8s 0.15s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.hero-sub strong {
|
||||
font-weight: 600;
|
||||
color: #95cff5;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font: 800 58px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 56px;
|
||||
text-shadow: 0 4px 30px rgba(0, 0, 0, 0.7);
|
||||
animation: rise 0.8s 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
266
assets/css/homepage/_profile.scss
Normal file
@@ -0,0 +1,266 @@
|
||||
.profile-page {
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 24px 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 28px;
|
||||
padding: 32px 36px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(35, 111, 135, 0.2);
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.45) 0%, rgba(173, 10, 5, 0.3) 100%);
|
||||
border: 2px solid rgba(35, 111, 135, 0.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font: 800 28px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
letter-spacing: 2px;
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(35, 111, 135, 0.08),
|
||||
0 0 24px rgba(35, 111, 135, 0.2);
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font: 800 32px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.profile-email {
|
||||
font: 400 14px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
letter-spacing: 0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
i { font-size: 11px; opacity: 0.7; }
|
||||
}
|
||||
|
||||
.profile-role {
|
||||
font: 600 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2.5px;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i { font-size: 10px; }
|
||||
}
|
||||
|
||||
.profile-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.profile-stat {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
border-radius: 8px;
|
||||
padding: 24px 16px 20px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: border-color 200ms ease, background 200ms ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: rgba(35, 111, 135, 0.3);
|
||||
}
|
||||
|
||||
&--win {
|
||||
border-color: rgba(42, 158, 96, 0.18);
|
||||
&:hover { border-color: rgba(42, 158, 96, 0.45); }
|
||||
}
|
||||
|
||||
&--loss {
|
||||
border-color: rgba(173, 10, 5, 0.18);
|
||||
&:hover { border-color: rgba(173, 10, 5, 0.45); }
|
||||
}
|
||||
|
||||
&--bomb {
|
||||
border-color: rgba(246, 125, 82, 0.18);
|
||||
&:hover { border-color: rgba(246, 125, 82, 0.45); }
|
||||
}
|
||||
}
|
||||
|
||||
.profile-stat__icon {
|
||||
font-size: 18px;
|
||||
color: rgba(255, 255, 255, 0.15);
|
||||
margin-bottom: 2px;
|
||||
|
||||
.profile-stat--win & { color: rgba(94, 232, 154, 0.3); }
|
||||
.profile-stat--loss & { color: rgba(246, 125, 82, 0.3); }
|
||||
.profile-stat--bomb & { color: rgba(246, 125, 82, 0.25); }
|
||||
}
|
||||
|
||||
.profile-stat__value {
|
||||
display: block;
|
||||
font: 800 40px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
|
||||
.profile-stat--win & { color: #5ee89a; }
|
||||
.profile-stat--loss & { color: #f67d52; }
|
||||
.profile-stat--bomb & { color: rgba(246, 125, 82, 0.8); }
|
||||
}
|
||||
|
||||
.profile-stat__label {
|
||||
font: 600 10px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.profile-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.profile-section__title {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
color: rgba(149, 207, 245, 0.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.profile-games {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.profile-game {
|
||||
display: grid;
|
||||
grid-template-columns: 26px 76px 22px 1fr 18px auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 11px 16px;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid rgba(255, 255, 255, 0.055);
|
||||
border-left-width: 3px;
|
||||
font: 500 14px 'Rajdhani', sans-serif;
|
||||
transition: background 180ms ease;
|
||||
|
||||
&:hover { background: rgba(255, 255, 255, 0.045); }
|
||||
|
||||
&--win { border-left-color: rgba(42, 158, 96, 0.55); }
|
||||
&--loss { border-left-color: rgba(173, 10, 5, 0.55); }
|
||||
&--draw { border-left-color: rgba(149, 207, 245, 0.25); }
|
||||
}
|
||||
|
||||
.profile-game__badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
font: 800 10px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 0;
|
||||
|
||||
.profile-game--win & { background: rgba(42, 158, 96, 0.18); color: #5ee89a; }
|
||||
.profile-game--loss & { background: rgba(173, 10, 5, 0.18); color: #f67d52; }
|
||||
.profile-game--draw & { background: rgba(149, 207, 245, 0.1); color: rgba(149, 207, 245, 0.65); }
|
||||
}
|
||||
|
||||
.profile-game__score {
|
||||
font: 700 14px 'Rajdhani', sans-serif;
|
||||
color: #fff;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.profile-game__vs {
|
||||
font: 400 10px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.22);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-game__opponent {
|
||||
color: rgba(149, 207, 245, 0.7);
|
||||
letter-spacing: 0.5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-game__color {
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.profile-game__date {
|
||||
font: 400 11px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
letter-spacing: 0.5px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-empty {
|
||||
text-align: center;
|
||||
padding: 48px 20px;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
|
||||
i {
|
||||
font-size: 40px;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
p {
|
||||
font: 400 15px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #95cff5;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: color 180ms;
|
||||
|
||||
&:hover { color: #c5e8ff; }
|
||||
}
|
||||
}
|
||||
23
assets/css/homepage/_reset.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
* {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
// Grid lives on html so it tiles across all pages including content pages
|
||||
background-color: #07090d;
|
||||
background-image:
|
||||
linear-gradient(rgba(35, 111, 135, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.1) 1px, transparent 1px);
|
||||
background-size: 46px 46px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
100
assets/css/homepage/_responsive.scss
Normal file
@@ -0,0 +1,100 @@
|
||||
@media screen and (max-width: 900px) {
|
||||
.hero h1 {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 20px 72px 18px;
|
||||
font-size: 24px;
|
||||
letter-spacing: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.hero--compact {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 36px 24px 44px;
|
||||
gap: 28px;
|
||||
|
||||
.hero-body { align-items: center; }
|
||||
}
|
||||
|
||||
.profile-stats {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 28px 24px;
|
||||
}
|
||||
|
||||
.profile-email,
|
||||
.profile-role {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.profile-game {
|
||||
grid-template-columns: 26px 64px 18px 1fr 14px;
|
||||
|
||||
.profile-game__date { display: none; }
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 48px 30px 36px;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-nav-label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-nav ul {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-copy {
|
||||
padding: 16px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.hero {
|
||||
padding: 60px 24px 140px;
|
||||
}
|
||||
|
||||
.hero-logo img {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.hero-logo {
|
||||
margin-bottom: 52px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 18px 48px 16px;
|
||||
font-size: 20px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
}
|
||||
60
assets/css/homepage/_tech.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
main {
|
||||
background: #07090d;
|
||||
}
|
||||
|
||||
.tech-section {
|
||||
padding: 48px 20px 72px;
|
||||
text-align: center;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.tech-label {
|
||||
font: 600 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 6px;
|
||||
color: rgba(255, 255, 255, 0.14);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.tech-link {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.tech-logos img {
|
||||
display: inline-block;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
object-fit: contain;
|
||||
margin: 8px 24px;
|
||||
// Force all logos to white, then tint with the game's blue on hover
|
||||
filter: brightness(0) invert(1) opacity(0.35);
|
||||
transition: filter 220ms ease, transform 220ms ease;
|
||||
}
|
||||
|
||||
.tech-logos img:hover {
|
||||
filter:
|
||||
brightness(0) invert(1)
|
||||
sepia(1) saturate(3) hue-rotate(175deg) brightness(1.1)
|
||||
opacity(0.9);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.tech-oss {
|
||||
margin-top: 36px;
|
||||
font: 400 15px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
letter-spacing: 0.5px;
|
||||
max-width: 680px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 0 24px;
|
||||
line-height: 1.7;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
color: rgba(220, 60, 50, 0.85);
|
||||
margin-right: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
24
assets/css/mineseeker/_back-button.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
.back-from-game {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
|
||||
-ms-transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
-webkit-transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||
transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||
}
|
||||
|
||||
.back-from-game img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.back-from-game:hover {
|
||||
-ms-transform: scale(1.2);
|
||||
-webkit-transform: scale(1.2);
|
||||
transform: scale(1.2);
|
||||
-webkit-transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||
transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||
}
|
||||
57
assets/css/mineseeker/_base.scss
Normal file
@@ -0,0 +1,57 @@
|
||||
html {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mine-container {
|
||||
background: url("/images/bg-mineseeker-0-outbg.jpg") no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both
|
||||
}
|
||||
|
||||
#mine-wrapper,
|
||||
#mine-wrapper * {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#mine-wrapper {
|
||||
display: table;
|
||||
width: 842px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper {
|
||||
background: #000;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
225
assets/css/mineseeker/_bomb.scss
Normal file
@@ -0,0 +1,225 @@
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control {
|
||||
background: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 0%, rgba(125, 185, 232, 0) 100%);
|
||||
background: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 0%, rgba(125, 185, 232, 0) 100%);
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.5) 0%, rgba(125, 185, 232, 0) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#007db9e8', GradientType=1);
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control > img {
|
||||
position: absolute;
|
||||
width: 55px;
|
||||
left: -5px;
|
||||
bottom: 10px;
|
||||
|
||||
-ms-transform: rotate(-15deg);
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .user-control-mines {
|
||||
display: inline-block;
|
||||
background: #FFFFFF;
|
||||
font-size: 25px;
|
||||
text-align: center;
|
||||
width: 45px;
|
||||
height: 35px;
|
||||
margin-left: 25px;
|
||||
margin-top: 5px;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-control .user-control-mines {
|
||||
color: #1a3955;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red .user-control .user-control-mines {
|
||||
color: #b10000;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .bomb-container {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
width: 65px;
|
||||
height: 45px;
|
||||
border: 1px solid #000;
|
||||
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .bomb-container.buzz:hover {
|
||||
-webkit-animation-name: hvr-buzz-out;
|
||||
animation-name: hvr-buzz-out;
|
||||
-webkit-animation-duration: 0.75s;
|
||||
animation-duration: 0.75s;
|
||||
-webkit-animation-timing-function: linear;
|
||||
animation-timing-function: linear;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
@-webkit-keyframes hvr-buzz-out {
|
||||
10% { -webkit-transform: translateX(3px) rotate(2deg); transform: translateX(3px) rotate(2deg); }
|
||||
20% { -webkit-transform: translateX(-3px) rotate(-2deg); transform: translateX(-3px) rotate(-2deg); }
|
||||
30% { -webkit-transform: translateX(3px) rotate(2deg); transform: translateX(3px) rotate(2deg); }
|
||||
40% { -webkit-transform: translateX(-3px) rotate(-2deg); transform: translateX(-3px) rotate(-2deg); }
|
||||
50% { -webkit-transform: translateX(2px) rotate(1deg); transform: translateX(2px) rotate(1deg); }
|
||||
60% { -webkit-transform: translateX(-2px) rotate(-1deg); transform: translateX(-2px) rotate(-1deg); }
|
||||
70% { -webkit-transform: translateX(2px) rotate(1deg); transform: translateX(2px) rotate(1deg); }
|
||||
80% { -webkit-transform: translateX(-2px) rotate(-1deg); transform: translateX(-2px) rotate(-1deg); }
|
||||
90% { -webkit-transform: translateX(1px) rotate(0); transform: translateX(1px) rotate(0); }
|
||||
100% { -webkit-transform: translateX(-1px) rotate(0); transform: translateX(-1px) rotate(0); }
|
||||
}
|
||||
|
||||
@keyframes hvr-buzz-out {
|
||||
10% { -webkit-transform: translateX(3px) rotate(2deg); transform: translateX(3px) rotate(2deg); }
|
||||
20% { -webkit-transform: translateX(-3px) rotate(-2deg); transform: translateX(-3px) rotate(-2deg); }
|
||||
30% { -webkit-transform: translateX(3px) rotate(2deg); transform: translateX(3px) rotate(2deg); }
|
||||
40% { -webkit-transform: translateX(-3px) rotate(-2deg); transform: translateX(-3px) rotate(-2deg); }
|
||||
50% { -webkit-transform: translateX(2px) rotate(1deg); transform: translateX(2px) rotate(1deg); }
|
||||
60% { -webkit-transform: translateX(-2px) rotate(-1deg); transform: translateX(-2px) rotate(-1deg); }
|
||||
70% { -webkit-transform: translateX(2px) rotate(1deg); transform: translateX(2px) rotate(1deg); }
|
||||
80% { -webkit-transform: translateX(-2px) rotate(-1deg); transform: translateX(-2px) rotate(-1deg); }
|
||||
90% { -webkit-transform: translateX(1px) rotate(0); transform: translateX(1px) rotate(0); }
|
||||
100% { -webkit-transform: translateX(-1px) rotate(0); transform: translateX(-1px) rotate(0); }
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .bomb-container .bomb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .bomb-container .bomb img {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .bomb-container:hover .bomb img {
|
||||
-webkit-animation-name: hvr-buzz;
|
||||
animation-name: hvr-buzz;
|
||||
-webkit-animation-duration: 0.15s;
|
||||
animation-duration: 0.15s;
|
||||
-webkit-animation-timing-function: linear;
|
||||
animation-timing-function: linear;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control .bomb-container .bomb {
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-control .bomb-container .bomb {
|
||||
background: rgb(131, 194, 245);
|
||||
background: -moz-linear-gradient(top, rgba(131, 194, 245, 1) 0%, rgba(108, 190, 230, 1) 39%, rgba(221, 255, 252, 1) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(131, 194, 245, 1) 0%, rgba(108, 190, 230, 1) 39%, rgba(221, 255, 252, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(131, 194, 245, 1) 0%, rgba(108, 190, 230, 1) 39%, rgba(221, 255, 252, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#83c2f5', endColorstr='#ddfffc', GradientType=0);
|
||||
border: 3px solid #0b538e;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red .user-control .bomb-container .bomb {
|
||||
background: rgb(255, 175, 159);
|
||||
background: -moz-linear-gradient(top, rgba(255, 175, 159, 1) 0%, rgba(231, 113, 7, 1) 54%, rgba(231, 113, 7, 1) 54%, rgba(237, 172, 16, 1) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(255, 175, 159, 1) 0%, rgba(231, 113, 7, 1) 54%, rgba(231, 113, 7, 1) 54%, rgba(237, 172, 16, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(255, 175, 159, 1) 0%, rgba(231, 113, 7, 1) 54%, rgba(231, 113, 7, 1) 54%, rgba(237, 172, 16, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffaf9f', endColorstr='#edac10', GradientType=0);
|
||||
border: 3px solid #c9221c;
|
||||
}
|
||||
|
||||
// Resign button
|
||||
|
||||
#mine-wrapper .game-wrapper .users .resign {
|
||||
background: rgba(70, 73, 66, 1);
|
||||
background: -moz-linear-gradient(top, rgba(70, 73, 66, 1) 0%, rgba(140, 138, 139, 1) 69%, rgba(96, 89, 97, 1) 100%);
|
||||
background: -webkit-gradient(left top, left bottom, color-stop(0%, rgba(70, 73, 66, 1)), color-stop(69%, rgba(140, 138, 139, 1)), color-stop(100%, rgba(96, 89, 97, 1)));
|
||||
background: -webkit-linear-gradient(top, rgba(70, 73, 66, 1) 0%, rgba(140, 138, 139, 1) 69%, rgba(96, 89, 97, 1) 100%);
|
||||
background: -o-linear-gradient(top, rgba(70, 73, 66, 1) 0%, rgba(140, 138, 139, 1) 69%, rgba(96, 89, 97, 1) 100%);
|
||||
background: -ms-linear-gradient(top, rgba(70, 73, 66, 1) 0%, rgba(140, 138, 139, 1) 69%, rgba(96, 89, 97, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(70, 73, 66, 1) 0%, rgba(140, 138, 139, 1) 69%, rgba(96, 89, 97, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#464942', endColorstr='#605961', GradientType=0);
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 95%;
|
||||
height: 50px;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border: 3px solid #484742;
|
||||
color: #fff;
|
||||
margin: 10px auto 0 auto;
|
||||
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-webkit-transition: all 250ms ease-in-out;
|
||||
-moz-transition: all 250ms ease-in-out;
|
||||
-o-transition: all 250ms ease-in-out;
|
||||
transition: all 250ms ease-in-out;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .resign:hover {
|
||||
background: rgba(70, 73, 66, 1);
|
||||
color: #FFFFFF;
|
||||
transition: all 250ms ease-in-out;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .resign.disabled {
|
||||
background: rgba(70, 73, 66, 1);
|
||||
color: #848484;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .resign.disabled:hover {
|
||||
background: rgba(70, 73, 66, 1);
|
||||
color: #848484;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .resign .resign-shine {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
background: -moz-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
background: -webkit-gradient(left top, left bottom, color-stop(0%, rgba(255, 255, 255, 1)), color-stop(100%, rgba(0, 0, 0, 0)));
|
||||
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
background: -o-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
background: -ms-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#000000', GradientType=0);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 33%;
|
||||
|
||||
opacity: 0.7;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
transition: all 250ms ease-in-out;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .resign:hover .resign-shine,
|
||||
#mine-wrapper .game-wrapper .users .resign.disabled .resign-shine {
|
||||
display: none;
|
||||
transition: all 250ms ease-in-out;
|
||||
}
|
||||
209
assets/css/mineseeker/_grid.scss
Normal file
@@ -0,0 +1,209 @@
|
||||
#mine-wrapper .grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 643px;
|
||||
border: 1px solid #cac3e5;
|
||||
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid-container {
|
||||
background: #4E4E4E;
|
||||
padding: 15px 10px;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper > img.field-target {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 45px;
|
||||
top: -2.5px;
|
||||
left: -2.5px;
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper:hover > img.field-target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper > img.field-bomb-target {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper > img.field-blue-last,
|
||||
#mine-wrapper .grid .field-wrapper > img.field-red-last {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper > img.field-blue-last.last-clicked,
|
||||
#mine-wrapper .grid .field-wrapper > img.field-red-last.last-clicked {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field {
|
||||
background: #61defa;
|
||||
background: -moz-linear-gradient(left, #61defa 0%, #119dec 100%);
|
||||
background: -webkit-linear-gradient(left, #61defa 0%, #119dec 100%);
|
||||
background: linear-gradient(to right, #61defa 0%, #119dec 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#61defa', endColorstr='#119dec', GradientType=1);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid #51c2fe;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 35px;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field .field-corner {
|
||||
background: url('/images/bg-corner-outbg.png') no-repeat top left;
|
||||
background-size: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active {
|
||||
background: #fde717;
|
||||
background: -moz-linear-gradient(left, #fde717 0%, #f5b807 100%);
|
||||
background: -webkit-linear-gradient(left, #fde717 0%, #f5b807 100%);
|
||||
background: linear-gradient(to right, #fde717 0%, #f5b807 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fde717', endColorstr='#f5b807', GradientType=1);
|
||||
border: 2px solid #f6d762;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active .flag-number {
|
||||
-webkit-animation: bubbleNumber 500ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
animation: bubbleNumber 500ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@keyframes bubbleNumber {
|
||||
0% {
|
||||
background: #61defa;
|
||||
background: -moz-linear-gradient(left, #61defa 0%, #119dec 100%);
|
||||
background: -webkit-linear-gradient(left, #61defa 0%, #119dec 100%);
|
||||
background: linear-gradient(to right, #61defa 0%, #119dec 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#61defa', endColorstr='#119dec', GradientType=1);
|
||||
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active .flag-mine {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active .flag-mine > img {
|
||||
width: 75%;
|
||||
margin-left: 15px;
|
||||
|
||||
-ms-transform: rotate(7deg);
|
||||
-webkit-transform: rotate(7deg);
|
||||
transform: rotate(7deg);
|
||||
-webkit-animation: mineFlagLoad 500ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
animation: mineFlagLoad 500ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
}
|
||||
|
||||
@keyframes mineFlagLoad {
|
||||
0% {
|
||||
margin-bottom: 0;
|
||||
margin-left: 15px;
|
||||
-ms-transform: rotate(9deg);
|
||||
-webkit-transform: rotate(9deg);
|
||||
transform: rotate(9deg);
|
||||
}
|
||||
50% {
|
||||
margin-bottom: -5px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
100% {
|
||||
margin-bottom: 3px;
|
||||
margin-left: 0;
|
||||
-ms-transform: rotate(-9deg);
|
||||
-webkit-transform: rotate(-9deg);
|
||||
transform: rotate(-9deg);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active .flag-mine .flag-mine-base {
|
||||
position: absolute;
|
||||
background: #000000;
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
bottom: -12px;
|
||||
left: 50%;
|
||||
margin-left: -10.5px;
|
||||
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
-webkit-animation: mineBaseLoad 500ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
animation: mineBaseLoad 500ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
}
|
||||
|
||||
@keyframes mineBaseLoad {
|
||||
0% { margin-bottom: 0; }
|
||||
50% { margin-bottom: -5px; }
|
||||
100% { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active.mine {
|
||||
background: #61defa;
|
||||
background: -moz-linear-gradient(left, #61defa 0%, #119dec 100%);
|
||||
background: -webkit-linear-gradient(left, #61defa 0%, #119dec 100%);
|
||||
background: linear-gradient(to right, #61defa 0%, #119dec 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#61defa', endColorstr='#119dec', GradientType=1);
|
||||
border: 2px solid #51c2fe;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-1 { color: #0000ff; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-2 { color: #079433; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-3 { color: #fd1400; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-4 { color: #0c099e; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-5 { color: #7b4c01; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-6 { color: #008388; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-7 { color: #000000; }
|
||||
#mine-wrapper .grid .field-wrapper .field.active.color-8 { color: #ff0000; }
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field img {
|
||||
width: 80%;
|
||||
}
|
||||
90
assets/css/mineseeker/_mine-counter.scss
Normal file
@@ -0,0 +1,90 @@
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container {
|
||||
background: -moz-radial-gradient(center, ellipse cover, rgba(255, 252, 252, 1) 0%, rgba(255, 252, 252, 0.99) 1%, rgba(106, 106, 106, 0.39) 61%, rgba(106, 106, 106, 0) 100%);
|
||||
background: -webkit-radial-gradient(center, ellipse cover, rgba(255, 252, 252, 1) 0%, rgba(255, 252, 252, 0.99) 1%, rgba(106, 106, 106, 0.39) 61%, rgba(106, 106, 106, 0) 100%);
|
||||
background: radial-gradient(ellipse at center, rgba(255, 252, 252, 1) 0%, rgba(255, 252, 252, 0.99) 1%, rgba(106, 106, 106, 0.39) 61%, rgba(106, 106, 106, 0) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcfc', endColorstr='#006a6a6a', GradientType=1);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 72% 179%;
|
||||
position: relative;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container i {
|
||||
font-size: 27px;
|
||||
color: #b1b1b3;
|
||||
margin-top: 3px;
|
||||
|
||||
text-shadow: 0 0 3px #000000;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container i:first-child {
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container i:last-child {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container .active-mines {
|
||||
background: -moz-linear-gradient(top, rgba(0, 0, 0, 1) 0%, rgba(135, 136, 131, 1) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(0, 0, 0, 1) 0%, rgba(135, 136, 131, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(135, 136, 131, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3e3f41', endColorstr='#878883', GradientType=0);
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: -7.5px;
|
||||
left: 50%;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 25px;
|
||||
line-height: 39px;
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
border: 5px solid #000000;
|
||||
margin-left: -25px;
|
||||
|
||||
z-index: 100;
|
||||
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container .active-mines.found-mine {
|
||||
-webkit-animation: bubbleLeftMine 750ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
animation: bubbleLeftMine 750ms cubic-bezier(.36, .07, .19, .97) both;
|
||||
}
|
||||
|
||||
@keyframes bubbleLeftMine {
|
||||
0% { -webkit-transform: scale(1); transform: scale(1); }
|
||||
50% { border-color: #2e3337; -webkit-transform: scale(2); transform: scale(2); }
|
||||
100% { -webkit-transform: scale(1); transform: scale(1); }
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container .active-mines .active-mines-shine {
|
||||
background: -moz-linear-gradient(top, rgba(213, 214, 216, 1) 0%, rgba(106, 106, 106, 1) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(213, 214, 216, 1) 0%, rgba(106, 106, 106, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(213, 214, 216, 1) 0%, rgba(106, 106, 106, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d5d6d8', endColorstr='#6a6a6a', GradientType=0);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
margin-left: -14.5px;
|
||||
|
||||
z-index: 101;
|
||||
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container .active-mines .active-mines-nbr {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
z-index: 102;
|
||||
}
|
||||
137
assets/css/mineseeker/_overlay.scss
Normal file
@@ -0,0 +1,137 @@
|
||||
#mine-wrapper .game-wrapper .game-overlay {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 200;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window {
|
||||
background: rgba(204, 204, 204, 0.8);
|
||||
border: 5px solid rgba(255, 255, 255, 0.5);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
text-align: center;
|
||||
color: #354d6a;
|
||||
width: 660px;
|
||||
padding: 10px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h1 {
|
||||
font-weight: bold;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h3 {
|
||||
font-size: 16px;
|
||||
color: #386e8c;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-invite {
|
||||
padding: 0 4px 4px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-invite-label {
|
||||
font-size: 13px;
|
||||
color: #386e8c;
|
||||
margin: 0 0 8px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-url-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #d0e8f5;
|
||||
border: 1px solid #7ab8d8;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
margin-bottom: 12px;
|
||||
cursor: text;
|
||||
transition: border-color 200ms ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #236f87;
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-url-icon {
|
||||
color: #236f87;
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-url-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
height: 40px;
|
||||
color: #1a4a6a;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.3px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: text;
|
||||
min-width: 0;
|
||||
|
||||
&::selection {
|
||||
background: rgba(35, 111, 135, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-copy-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 1px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 9px 32px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 220ms ease;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 10px rgba(35, 111, 135, 0.5);
|
||||
}
|
||||
|
||||
&.copied {
|
||||
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||
border-color: #2a9e60;
|
||||
color: #a0f0c0;
|
||||
box-shadow: 0 0 8px rgba(26, 104, 68, 0.5);
|
||||
}
|
||||
}
|
||||
41
assets/css/mineseeker/_responsive.scss
Normal file
@@ -0,0 +1,41 @@
|
||||
@media screen and (max-width: 900px) {
|
||||
#mine-wrapper .game-wrapper .users {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mine-wrapper {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper {
|
||||
width: 100%;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid-container {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper {
|
||||
width: 6.25%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper > img.field-target {
|
||||
width: 105%;
|
||||
top: -2.5%;
|
||||
left: -2.5%;
|
||||
}
|
||||
|
||||
#mine-wrapper .grid .field-wrapper .field {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
72
assets/css/mineseeker/_timer.scss
Normal file
@@ -0,0 +1,72 @@
|
||||
#mine-wrapper .game-timer-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-timer {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 115px;
|
||||
border-radius: 8px;
|
||||
padding: 8px 18px;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-weight: bold;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
// Red – waiting
|
||||
#mine-wrapper .game-timer.red-timer {
|
||||
background: linear-gradient(to bottom, #4a0603 0%, #6b2515 100%);
|
||||
border-color: #7a1e10;
|
||||
color: rgba(246, 125, 82, 0.55);
|
||||
}
|
||||
|
||||
// Red – active (thinking)
|
||||
#mine-wrapper .game-timer.red-timer.active {
|
||||
background: linear-gradient(to bottom, #ad0a05 0%, #f67d52 100%);
|
||||
border-color: #ff9b6b;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 16px rgba(173, 10, 5, 0.75), 0 0 5px rgba(246, 125, 82, 0.5);
|
||||
}
|
||||
|
||||
// Blue – waiting
|
||||
#mine-wrapper .game-timer.blue-timer {
|
||||
background: linear-gradient(to bottom, #0b2530 0%, #163d55 100%);
|
||||
border-color: #173650;
|
||||
color: rgba(149, 207, 245, 0.55);
|
||||
}
|
||||
|
||||
// Blue – active (thinking)
|
||||
#mine-wrapper .game-timer.blue-timer.active {
|
||||
background: linear-gradient(to bottom, #236f87 0%, #95cff5 100%);
|
||||
border-color: #b8e5ff;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 16px rgba(35, 111, 135, 0.75), 0 0 5px rgba(149, 207, 245, 0.5);
|
||||
}
|
||||
|
||||
#mine-wrapper .game-timer .timer-icon {
|
||||
font-size: 15px;
|
||||
opacity: 0.7;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-timer.active .timer-icon {
|
||||
opacity: 1;
|
||||
animation: timer-icon-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes timer-icon-pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.85; }
|
||||
50% { transform: scale(1.2); opacity: 1; }
|
||||
}
|
||||
|
||||
#mine-wrapper .game-timer .timer-display {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 20px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
153
assets/css/mineseeker/_users.scss
Normal file
@@ -0,0 +1,153 @@
|
||||
#mine-wrapper .game-wrapper .users {
|
||||
width: 180px;
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container {
|
||||
background: #FFFFFF;
|
||||
height: 40%;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
|
||||
z-index: 99;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue {
|
||||
background: rgb(35, 111, 135);
|
||||
background: -moz-linear-gradient(top, rgba(35, 111, 135, 1) 0%, rgba(149, 207, 245, 1) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(35, 111, 135, 1) 0%, rgba(149, 207, 245, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(35, 111, 135, 1) 0%, rgba(149, 207, 245, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#236f87', endColorstr='#95cff5', GradientType=0);
|
||||
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red {
|
||||
background: rgb(173, 10, 5);
|
||||
background: -moz-linear-gradient(top, rgba(173, 10, 5, 1) 0%, rgba(246, 125, 82, 1) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(173, 10, 5, 1) 0%, rgba(246, 125, 82, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(173, 10, 5, 1) 0%, rgba(246, 125, 82, 1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ad0a05', endColorstr='#f67d52', GradientType=0);
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-header {
|
||||
background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.7) 39%, rgba(255, 255, 255, 0.21) 87%, rgba(0, 0, 0, 0) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.7) 39%, rgba(255, 255, 255, 0.21) 87%, rgba(0, 0, 0, 0) 100%);
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.7) 39%, rgba(255, 255, 255, 0.21) 87%, rgba(0, 0, 0, 0) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#00000000', GradientType=0);
|
||||
position: relative;
|
||||
font: bolder 25px 'Changa One', cursive;
|
||||
letter-spacing: 5px;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 6px 5px 20px 5px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-webkit-text-shadow: 1px 1px 0 #FFF;
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-header {
|
||||
color: #236f87;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red .user-header {
|
||||
color: #AD0A05;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-header > img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
width: 40%;
|
||||
margin-left: -20%;
|
||||
margin-bottom: -25%;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-header > img.user-cursor {
|
||||
display: block;
|
||||
width: 30%;
|
||||
top: 20px;
|
||||
left: 10px;
|
||||
margin-left: 0;
|
||||
|
||||
-webkit-animation: cursorJumping 1.2s cubic-bezier(.36, .07, .19, .97) infinite;
|
||||
animation: cursorJumping 1.2s cubic-bezier(.36, .07, .19, .97) infinite;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-header > img.user-cursor::after {
|
||||
content: '';
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #1A6844;
|
||||
animation: animate .5s linear infinite;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@keyframes cursorJumping {
|
||||
0% { top: 15px; }
|
||||
50% { top: 25px; }
|
||||
100% { top: 15px; }
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-name {
|
||||
min-height: 30px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: 3px 0;
|
||||
margin: 0 5px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-name {
|
||||
border-top: 1px dashed #0b3776;
|
||||
border-bottom: 1px dashed #0b3776;
|
||||
color: #0b3776;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red .user-name {
|
||||
color: #fdf612;
|
||||
border-top: 1px dashed #fdf612;
|
||||
border-bottom: 1px dashed #fdf612;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-caret {
|
||||
height: 30px;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-caret > i {
|
||||
color: #0b3776;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red .user-caret > i {
|
||||
color: #fdf612;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-desc {
|
||||
height: 65px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-desc {
|
||||
color: #0b3776;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container.user-red .user-desc {
|
||||
color: #fdf612;
|
||||
}
|
||||
@@ -1,592 +1,14 @@
|
||||
// ── Reset ────────────────────────────────────────────────────────────────────
|
||||
|
||||
* {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
// Grid lives on html so it tiles across all pages including content pages
|
||||
background-color: #07090d;
|
||||
background-image:
|
||||
linear-gradient(rgba(35, 111, 135, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.1) 1px, transparent 1px);
|
||||
background-size: 46px 46px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// ── Hero ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
// Minesweeper grid texture
|
||||
background-color: #07090d;
|
||||
background-image:
|
||||
linear-gradient(rgba(35, 111, 135, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.1) 1px, transparent 1px);
|
||||
background-size: 46px 46px;
|
||||
}
|
||||
|
||||
// Deep radial vignette – grid fades toward the centre
|
||||
header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(
|
||||
ellipse 85% 75% at 50% 50%,
|
||||
#07090d 10%,
|
||||
transparent 75%
|
||||
);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Smoke at the bottom so header bleeds into body
|
||||
header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background: linear-gradient(to bottom, transparent, #07090d);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// ── Hero section ─────────────────────────────────────────────────────────────
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
padding: 80px 40px 160px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
// Decorative glow blobs in opposite corners
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
left: -60px;
|
||||
width: 420px;
|
||||
height: 420px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(173, 10, 5, 0.09) 0%, transparent 65%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
right: -60px;
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(35, 111, 135, 0.1) 0%, transparent 65%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
// Logo
|
||||
.hero-logo {
|
||||
display: block;
|
||||
margin-bottom: 72px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
animation: appear 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.hero-logo img {
|
||||
width: 400px;
|
||||
max-width: 82vw;
|
||||
filter:
|
||||
drop-shadow(0 0 40px rgba(35, 111, 135, 0.35))
|
||||
drop-shadow(0 6px 20px rgba(0, 0, 0, 0.8));
|
||||
}
|
||||
|
||||
// Body text block
|
||||
.hero-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font: 300 17px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
letter-spacing: 3px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 18px;
|
||||
animation: rise 0.8s 0.15s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.hero-sub strong {
|
||||
font-weight: 600;
|
||||
color: #95cff5;
|
||||
}
|
||||
|
||||
.hero-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(149, 207, 245, 0.25);
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font: 800 58px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 56px;
|
||||
text-shadow: 0 4px 30px rgba(0, 0, 0, 0.7);
|
||||
animation: rise 0.8s 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
// ── CTA button ───────────────────────────────────────────────────────────────
|
||||
|
||||
.hero-cta {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font: 800 28px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 6px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
padding: 22px 100px 20px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(246, 125, 82, 0.25);
|
||||
|
||||
background: linear-gradient(to bottom, #b30c06 0%, #d63d15 50%, #f67d52 100%);
|
||||
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(173, 10, 5, 0.2),
|
||||
0 0 30px rgba(173, 10, 5, 0.35),
|
||||
0 6px 24px rgba(0, 0, 0, 0.5),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
||||
|
||||
transition: transform 220ms ease, box-shadow 220ms ease, letter-spacing 220ms ease;
|
||||
animation: rise 0.8s 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
// Outer glow layer (blurred duplicate, always visible)
|
||||
.hero-cta::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(to bottom, #ad0a05, #f67d52);
|
||||
filter: blur(18px);
|
||||
opacity: 0.3;
|
||||
z-index: -1;
|
||||
transition: opacity 220ms ease;
|
||||
}
|
||||
|
||||
.hero-cta:hover {
|
||||
transform: translateY(-4px);
|
||||
letter-spacing: 8px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(246, 125, 82, 0.3),
|
||||
0 0 50px rgba(173, 10, 5, 0.65),
|
||||
0 10px 32px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.hero-cta:hover::before {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.hero-cta:active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// Version / copyright line
|
||||
.hero-meta {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font: 400 12px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 58px;
|
||||
animation: rise 0.8s 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.hero-meta a {
|
||||
color: rgba(149, 207, 245, 0.65);
|
||||
text-decoration: none;
|
||||
transition: color 180ms;
|
||||
}
|
||||
|
||||
.hero-meta a:hover {
|
||||
color: #95cff5;
|
||||
}
|
||||
|
||||
// ── Compact hero (sub-pages) ─────────────────────────────────────────────────
|
||||
|
||||
.hero--compact {
|
||||
min-height: unset;
|
||||
padding: 36px 60px 48px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: left;
|
||||
gap: 52px;
|
||||
|
||||
&::before, &::after { display: none; }
|
||||
|
||||
.hero-logo {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
img { width: 180px; }
|
||||
}
|
||||
|
||||
.hero-body {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 12px 52px 10px;
|
||||
font-size: 18px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// Also shrink the bottom fade on sub-pages
|
||||
header:has(.hero--compact)::after {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
// ── Animations ───────────────────────────────────────────────────────────────
|
||||
|
||||
@keyframes appear {
|
||||
from { opacity: 0; transform: scale(0.94); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
from { opacity: 0; transform: translateY(16px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
// ── Content pages (terms, privacy, contact) ──────────────────────────────────
|
||||
|
||||
main div.txt {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 60px 40px 80px;
|
||||
}
|
||||
|
||||
main div.txt h2 {
|
||||
font: bold 28px 'Rajdhani', sans-serif;
|
||||
color: #ffffff;
|
||||
margin-bottom: 30px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
main div.txt h3 {
|
||||
font: bold 17px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
margin: 28px 0 10px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
main div.txt p,
|
||||
main div.txt li {
|
||||
font: 400 15px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
main div.txt a {
|
||||
color: #95cff5;
|
||||
text-decoration: none;
|
||||
transition: color 180ms;
|
||||
|
||||
&:hover { color: #c5e8ff; }
|
||||
}
|
||||
|
||||
// ── Technologies strip ────────────────────────────────────────────────────────
|
||||
|
||||
main {
|
||||
background: #07090d;
|
||||
}
|
||||
|
||||
.tech-section {
|
||||
padding: 48px 20px 72px;
|
||||
text-align: center;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.tech-label {
|
||||
font: 600 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 6px;
|
||||
color: rgba(255, 255, 255, 0.14);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.tech-logos img {
|
||||
display: inline-block;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
object-fit: contain;
|
||||
margin: 8px 24px;
|
||||
// Force all logos to white, then tint with the game's blue
|
||||
filter: brightness(0) invert(1) opacity(0.35);
|
||||
transition: filter 220ms ease, transform 220ms ease;
|
||||
}
|
||||
|
||||
.tech-logos img:hover {
|
||||
// Bright white → blue-tinted on hover
|
||||
filter:
|
||||
brightness(0) invert(1)
|
||||
sepia(1) saturate(3) hue-rotate(175deg) brightness(1.1)
|
||||
opacity(0.9);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
// ── Footer ───────────────────────────────────────────────────────────────────
|
||||
|
||||
footer {
|
||||
background: #040608;
|
||||
border-top: 1px solid rgba(35, 111, 135, 0.12);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 60px 60px 52px;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
// Left: brand block
|
||||
.footer-brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
opacity: 0.55;
|
||||
filter:
|
||||
drop-shadow(0 0 12px rgba(35, 111, 135, 0.4))
|
||||
brightness(1.1);
|
||||
transition: opacity 250ms ease, filter 250ms ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
filter:
|
||||
drop-shadow(0 0 20px rgba(35, 111, 135, 0.65))
|
||||
brightness(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.footer-name {
|
||||
font: 700 22px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
font: 400 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.7);
|
||||
letter-spacing: 0.5px;
|
||||
max-width: 240px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// Right: navigation
|
||||
.footer-nav-label {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.footer-nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.footer-nav ul li a {
|
||||
display: block;
|
||||
font: 500 15px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
white-space: nowrap;
|
||||
padding: 6px 0;
|
||||
transition: color 180ms ease, letter-spacing 180ms ease;
|
||||
|
||||
&:hover {
|
||||
color: #95cff5;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom copyright bar
|
||||
.footer-copy {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding: 16px 60px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
|
||||
p {
|
||||
font: 400 11px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
text-decoration: none;
|
||||
transition: color 180ms;
|
||||
|
||||
&:hover { color: #95cff5; }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Responsive ───────────────────────────────────────────────────────────────
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.hero--compact {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 36px 24px 44px;
|
||||
gap: 28px;
|
||||
|
||||
.hero-body { align-items: center; }
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 48px 30px 36px;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-nav-label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-nav ul {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-copy {
|
||||
padding: 16px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.hero h1 {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 20px 72px 18px;
|
||||
font-size: 24px;
|
||||
letter-spacing: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.hero {
|
||||
padding: 60px 24px 140px;
|
||||
}
|
||||
|
||||
.hero-logo img {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.hero-logo {
|
||||
margin-bottom: 52px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 18px 48px 16px;
|
||||
font-size: 20px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@use 'homepage/reset';
|
||||
@use 'homepage/animations';
|
||||
@use 'homepage/header';
|
||||
@use 'homepage/hero';
|
||||
@use 'homepage/hero-compact';
|
||||
@use 'homepage/cta';
|
||||
@use 'homepage/auth-bar';
|
||||
@use 'homepage/auth';
|
||||
@use 'homepage/content';
|
||||
@use 'homepage/features';
|
||||
@use 'homepage/tech';
|
||||
@use 'homepage/footer';
|
||||
@use 'homepage/profile';
|
||||
@use 'homepage/responsive';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Rajdhani:300,400,500,600,700&subset=latin-ext');
|
||||
@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css');
|
||||
@import "style";
|
||||
@import "style.homepage";
|
||||
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
||||
password_hashers:
|
||||
App\Entity\User:
|
||||
algorithm: auto
|
||||
|
||||
providers:
|
||||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: username
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_user_provider
|
||||
user_checker: App\Security\UserChecker
|
||||
form_login:
|
||||
login_path: MineSeekerBundle_login
|
||||
check_path: MineSeekerBundle_login
|
||||
default_target_path: MineSeekerBundle_homepage
|
||||
username_parameter: _username
|
||||
password_parameter: _password
|
||||
enable_csrf: true
|
||||
logout:
|
||||
path: MineSeekerBundle_logout
|
||||
target: MineSeekerBundle_homepage
|
||||
remember_me:
|
||||
secret: '%kernel.secret%'
|
||||
lifetime: 604800
|
||||
remember_me_parameter: _remember_me
|
||||
|
||||
# activate different ways to authenticate
|
||||
|
||||
# http_basic: true
|
||||
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
|
||||
|
||||
# form_login: true
|
||||
# https://symfony.com/doc/current/security/form_login_setup.html
|
||||
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB |
BIN
public/images/msn-minesweeper.png
Normal file
|
After Width: | Height: | Size: 590 KiB |
1
public/images/technologies/archlinux.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="#ffffff" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Arch Linux</title><path d="M11.39.605C10.376 3.092 9.764 4.72 8.635 7.132c.693.734 1.543 1.589 2.923 2.554-1.484-.61-2.496-1.224-3.252-1.86C6.86 10.842 4.596 15.138 0 23.395c3.612-2.085 6.412-3.37 9.021-3.862a6.61 6.61 0 01-.171-1.547l.003-.115c.058-2.315 1.261-4.095 2.687-3.973 1.426.12 2.534 2.096 2.478 4.409a6.52 6.52 0 01-.146 1.243c2.58.505 5.352 1.787 8.914 3.844-.702-1.293-1.33-2.459-1.929-3.57-.943-.73-1.926-1.682-3.933-2.713 1.38.359 2.367.772 3.137 1.234-6.09-11.334-6.582-12.84-8.67-17.74zM22.898 21.36v-.623h-.234v-.084h.562v.084h-.234v.623h.331v-.707h.142l.167.5.034.107a2.26 2.26 0 01.038-.114l.17-.493H24v.707h-.091v-.593l-.206.593h-.084l-.205-.602v.602h-.091"/></svg>
|
||||
|
After Width: | Height: | Size: 780 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -1,18 +1,12 @@
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 765 144" xml:space="preserve">
|
||||
<style>
|
||||
.st2{fill:#495466}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1=".0353619" y1="72" x2="144.0354" y2="72">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 144">
|
||||
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1=".035" y1="72" x2="144.035" y2="72">
|
||||
<stop offset="0" stop-color="#76c8dd"/>
|
||||
<stop offset="1" stop-color="#2ab3d7"/>
|
||||
</linearGradient>
|
||||
<path d="M72 144c-39.7 0-72-32.3-72-72S32.3 0 72 0s72 32.3 72 72-32.3 72-72 72zM72 6.1C35.7 6.1 6.1 35.7 6.1 72s29.6 65.9 65.9 65.9 66-29.6 66-65.9S108.4 6.1 72 6.1z" fill="url(#SVGID_1_)"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="15.4924" y1="71.2317" x2="128.5783" y2="71.2317">
|
||||
<path d="M72 144C32.3 144 0 111.7 0 72S32.3 0 72 0s72 32.3 72 72-32.3 72-72 72zm0-137.9C35.7 6.1 6.1 35.7 6.1 72s29.6 65.9 65.9 65.9 66-29.6 66-65.9S108.4 6.1 72 6.1z" fill="url(#a)"/>
|
||||
<linearGradient id="b" gradientUnits="userSpaceOnUse" x1="15.492" y1="71.232" x2="128.578" y2="71.232">
|
||||
<stop offset="0" stop-color="#76c8dd"/>
|
||||
<stop offset="1" stop-color="#2ab3d7"/>
|
||||
</linearGradient>
|
||||
<path d="M72 14.7c-31.2 0-56.5 25.3-56.5 56.5 0 9.7 2.5 18.9 6.8 26.9 1.5-1.1 3.1-2.2 4.9-3.3 31-18.5 58.4-33.7 76.6-62 0 0-1.3 32-19.7 46.9-4.1 3.3-7.6 5.9-10.9 8 9.3-4.9 17.4-10.5 23.7-18 0 0-.5 21.5-16.3 31-5.9 3.5-10.8 5.5-15.8 7 9-2 14.9-4.5 19.6-7.7-4 13.3-12.7 20.3-26.2 22.3-4.9.5-9.1-.5-13.5-1.5 8.1 4.5 17.4 7 27.3 7 31.2 0 56.5-25.3 56.5-56.5S103.3 14.7 72 14.7z" fill="url(#SVGID_2_)"/>
|
||||
<g>
|
||||
<path class="st2" d="M270 43.8c-1.7-1.9-2.9-3.2-3.5-3.9l-15-15.6c-2.9-3.2-6.2-4.9-10.2-4.9-3.9 0-7.5 1.6-10.6 4.9l-11.2 11.9-11.2-11.9c-2.8-3.2-6.1-4.9-10.2-4.9-3.8 0-7.2 1.6-10.2 4.9l-15 15.6c-.7.7-1.8 1.8-3.3 3.5-1.5 1.6-2.9 3.4-4 5.2s-1.7 4.2-1.8 6.9v56.2c0 3.9 1.3 7 3.8 9.2 2.6 2.2 5.6 3.3 9.1 3.3s6.6-1.1 9.1-3.3c2.6-2.2 3.8-5.3 3.8-9.2V54.9c0-4.7 3.8-8.5 8.5-8.5s8.5 3.8 8.5 8.5v56.9c0 4 1.3 7.2 3.8 9.4 2.6 2.3 5.6 3.4 9.1 3.4 3.4 0 6.4-1.1 9-3.4 2.6-2.3 3.8-5.4 3.8-9.4V55c0-4.8 3.9-8.6 8.6-8.6 4.8 0 8.6 3.9 8.6 8.6v56.7c0 3.9 1.3 7 3.8 9.2 2.6 2.2 5.6 3.3 9.1 3.3s6.6-1.1 9.1-3.3c2.6-2.2 3.8-5.3 3.8-9.2V55.6c-.2-2.3-.8-4.3-1.7-6.1-.6-1.9-1.9-3.8-3.6-5.7zM338.5 46.1c3.4 0 6.4-1.3 9-3.9 2.6-2.6 3.8-5.7 3.8-9.2 0-3.4-1.3-6.4-3.8-8.9s-5.5-3.8-9.1-3.8H303c-3.8 0-7.4 1-10.7 2.9-3.3 2-5.9 4.6-7.9 8-2 3.3-2.9 7-2.9 10.9V49c0 5.7.9 10.4 2.7 13.9 1.8 3.6 5.2 6.2 10.1 7.9-5.2 1.8-8.7 4.6-10.3 8.3-1.7 3.8-2.5 8.7-2.5 14.7v8c0 3.9 1 7.6 2.9 10.9 2 3.3 4.6 6 7.9 8 3.3 2 6.9 2.9 10.7 2.9h35.5c3.5-.2 6.6-1.5 9.1-4s3.8-5.4 3.8-8.8c0-3.6-1.3-6.7-3.8-9.2-2.5-2.5-5.5-3.8-9.1-3.8h-31.3V85.5H336c3.4 0 6.4-1.3 9-3.8 2.6-2.6 3.8-5.6 3.8-9 0-3.6-1.3-6.7-3.8-9.2-2.5-2.5-5.5-3.8-9.1-3.8h-28.8V46.1h31.4zM413 26.6c-4.5-4.2-9.7-6.3-15.5-6.3h-28.8c-3.6 0-6.7 1.3-9.2 3.8-2.5 2.5-3.8 5.6-3.8 9.2V111c0 3.5 1.3 6.6 3.8 9.1s5.6 3.8 9.2 3.8c3.5 0 6.5-1.3 9-3.8s3.7-5.5 3.7-9.1V73.1l25.2 30.7v7.2c0 3.4 1.2 6.4 3.7 9 2.5 2.6 5.5 3.8 9 3.8 3.4 0 6.4-1.3 8.8-3.8 2.5-2.5 3.7-5.5 3.7-9.1v-7.5c0-4.8-1-9-2.9-12.5s-5.1-6.7-9.5-9.4c8.7-4.3 13-11.4 13-21.1v-5.8c0-6-2.3-11.3-6.9-15.9L413 26.6zm-18.5 44.3h-13.1V58.7c0-7 5.6-12.6 12.6-12.6s12.6 5.6 12.6 12.6c0 6.7-5.4 12.2-12.1 12.2zM495.7 46.1c3.4 0 6.5-1.3 9.1-3.8 2.6-2.6 3.9-5.6 3.9-9 0-3.5-1.3-6.6-3.9-9.1-2.6-2.6-5.6-3.8-9.1-3.8H460c-3.9 0-7.6 1-10.9 2.9-3.3 2-6 4.6-8 8-2 3.3-2.9 7-2.9 10.9v59.6c0 3.9 1 7.6 2.9 10.9 2 3.3 4.6 6 8 8 3.3 2 7 2.9 10.9 2.9h35.4c3.5 0 6.6-1.2 9.1-3.7 2.6-2.5 3.8-5.5 3.8-9s-1.3-6.6-3.8-9.1c-2.6-2.5-5.6-3.8-9.1-3.8H464V46.1h31.7zM575.8 20c-3.5 0-6.6 1.3-9.1 3.9-2.6 2.6-3.8 5.6-3.8 9.1v53c0 6.6-5.3 11.9-11.9 11.9-6.6 0-11.9-5.3-11.9-11.9V33.6c0-3.8-1.2-7.1-3.7-9.7-2.5-2.6-5.6-3.9-9.3-3.9s-6.8 1.3-9.3 3.9c-2.5 2.6-3.7 5.7-3.7 9.4v66.4c0 4.8 1.1 9 3.4 12.7 2.3 3.6 5.3 6.5 9.2 8.5 3.9 2 8.2 3 13.1 3 5.7 0 10.5-1.5 14.5-4.4 4-2.9 6.9-7.2 8.6-12.8.2-.5.4-.7.6-.7.2 0 .3.2.3.7v4.4c0 3.6 1.3 6.7 3.8 9.2 2.6 2.5 5.6 3.8 9.1 3.8 3.6 0 6.7-1.3 9.3-3.8 2.6-2.5 3.8-5.6 3.8-9.2V33c0-3.4-1.2-6.3-3.5-8.8-2.3-2.6-5.9-4.2-9.5-4.2zM651.9 26.6c-4.5-4.2-9.7-6.3-15.5-6.3h-28.8c-3.6 0-6.7 1.3-9.2 3.8-2.5 2.5-3.8 5.6-3.8 9.2V111c0 3.5 1.3 6.6 3.8 9.1s5.6 3.8 9.2 3.8c3.5 0 6.5-1.3 9-3.8s3.7-5.5 3.7-9.1V73.1l25.2 30.7v7.2c0 3.4 1.2 6.4 3.7 9 2.5 2.6 5.5 3.8 9 3.8 3.4 0 6.4-1.3 8.8-3.8 2.5-2.5 3.7-5.5 3.7-9.1v-7.5c0-4.8-1-9-2.9-12.5s-5.1-6.7-9.5-9.4c8.7-4.3 13-11.4 13-21.1v-5.8c0-6-2.3-11.3-6.9-15.9l-12.5-12.1zm-18.5 44.3h-13.1V58.7c0-7 5.6-12.6 12.6-12.6s12.6 5.6 12.6 12.6c.1 6.7-5.4 12.2-12.1 12.2zM751.2 64.6C763.8 58 765 40.9 765 40.9c-12.6 13.8-31.4 19.9-52.6 27.5-5 1.8-7.9 1.1-9.6.7v-23h31.3c3.4 0 6.4-1.3 9-3.9 2.6-2.6 3.8-5.7 3.8-9.2 0-3.4-1.3-6.4-3.8-8.9s-5.5-3.8-9.1-3.8h-35.5c-3.8 0-7.4 1-10.7 2.9-3.3 2-5.9 4.6-7.9 8-2 3.3-2.9 7-2.9 10.9V49c0 5.7.9 10.4 2.7 13.9 1.8 3.6 5.2 6.2 10.1 7.9-5.2 1.8-8.7 4.6-10.3 8.3-1.7 3.8-2.5 8.7-2.5 14.7v8c0 3.9 1 7.6 2.9 10.9 2 3.3 4.6 6 7.9 8 3.3 2 6.9 2.9 10.7 2.9H734c3.5-.2 6.6-1.5 9.1-4s3.8-5.4 3.8-8.8c0-3.6-1.3-6.7-3.8-9.2-2.5-2.5-5.5-3.8-9.1-3.8h-31.3v-9.7c12.3-2.3 22.4-2.9 30.4-2.7 9.2-.1 15.1-3.1 18-10-3.2 1.4-7.2 2.3-13.4 2.7 3.3-.4 6.7-1.1 10.7-2.6 10.8-3.9 11.4-15.4 11.4-15.4-4.3 3.5-9.9 6-16.2 7.9 2.3-.8 4.8-1.9 7.6-3.4z"/>
|
||||
</g>
|
||||
<path d="M72 14.7C40.8 14.7 15.5 40 15.5 71.2c0 9.7 2.5 18.9 6.8 26.9 1.5-1.1 3.1-2.2 4.9-3.3 31-18.5 58.4-33.7 76.6-62 0 0-1.3 32-19.7 46.9-4.1 3.3-7.6 5.9-10.9 8 9.3-4.9 17.4-10.5 23.7-18 0 0-.5 21.5-16.3 31-5.9 3.5-10.8 5.5-15.8 7 9-2 14.9-4.5 19.6-7.7-4 13.3-12.7 20.3-26.2 22.3-4.9.5-9.1-.5-13.5-1.5 8.1 4.5 17.4 7 27.3 7 31.2 0 56.5-25.3 56.5-56.5S103.3 14.7 72 14.7z" fill="url(#b)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 1.0 KiB |
83
src/Controller/ProfileController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
/**
|
||||
* Class ProfileController
|
||||
*
|
||||
* @package App\Controller
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 11.
|
||||
*/
|
||||
class ProfileController extends AbstractController
|
||||
{
|
||||
#[Route('/profile', name: 'MineSeekerBundle_profile')]
|
||||
public function index(EntityManagerInterface $em): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
$finished = '(g.redPoints IS NOT NULL OR g.resign IS NOT NULL)';
|
||||
|
||||
$total = (int) $em->createQuery(
|
||||
"SELECT COUNT(g.id) FROM App\Entity\PlayedGame g
|
||||
WHERE (g.red = :u OR g.blue = :u) AND {$finished}"
|
||||
)->setParameter('u', $user)->getSingleScalarResult();
|
||||
|
||||
$wins = (int) $em->createQuery(
|
||||
"SELECT COUNT(g.id) FROM App\Entity\PlayedGame g WHERE (
|
||||
(g.red = :u AND g.redPoints > g.bluePoints AND g.resign IS NULL) OR
|
||||
(g.blue = :u AND g.bluePoints > g.redPoints AND g.resign IS NULL) OR
|
||||
(g.red = :u AND g.resign = 'blue') OR
|
||||
(g.blue = :u AND g.resign = 'red')
|
||||
)"
|
||||
)->setParameter('u', $user)->getSingleScalarResult();
|
||||
|
||||
$losses = (int) $em->createQuery(
|
||||
"SELECT COUNT(g.id) FROM App\Entity\PlayedGame g WHERE (
|
||||
(g.red = :u AND g.bluePoints > g.redPoints AND g.resign IS NULL) OR
|
||||
(g.blue = :u AND g.redPoints > g.bluePoints AND g.resign IS NULL) OR
|
||||
(g.red = :u AND g.resign = 'red') OR
|
||||
(g.blue = :u AND g.resign = 'blue')
|
||||
)"
|
||||
)->setParameter('u', $user)->getSingleScalarResult();
|
||||
|
||||
$bombs = (int) $em->createQuery(
|
||||
"SELECT COUNT(g.id) FROM App\Entity\PlayedGame g WHERE
|
||||
(g.red = :u AND g.redExplodedBomb = true) OR
|
||||
(g.blue = :u AND g.blueExplodedBomb = true)"
|
||||
)->setParameter('u', $user)->getSingleScalarResult();
|
||||
|
||||
$recent = $em->createQuery(
|
||||
"SELECT g FROM App\Entity\PlayedGame g
|
||||
LEFT JOIN g.red rr LEFT JOIN g.blue bb
|
||||
LEFT JOIN g.redAnon ra LEFT JOIN g.blueAnon ba
|
||||
WHERE (g.red = :u OR g.blue = :u) AND {$finished}
|
||||
ORDER BY g.updated DESC"
|
||||
)->setParameter('u', $user)->setMaxResults(10)->getResult();
|
||||
|
||||
return $this->render('Security/profile.html.twig', [
|
||||
'stats' => compact('total', 'wins', 'losses', 'bombs'),
|
||||
'recent' => $recent,
|
||||
]);
|
||||
}
|
||||
}
|
||||
155
src/Controller/SecurityController.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
/**
|
||||
* Class SecurityController
|
||||
*
|
||||
* @package App\Controller
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 11.
|
||||
*/
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route('/login', name: 'MineSeekerBundle_login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
if ($this->getUser()) {
|
||||
return $this->redirectToRoute('MineSeekerBundle_homepage');
|
||||
}
|
||||
|
||||
return $this->render('Security/login.html.twig', [
|
||||
'last_username' => $authenticationUtils->getLastUsername(),
|
||||
'error' => $authenticationUtils->getLastAuthenticationError(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/logout', name: 'MineSeekerBundle_logout', methods: ['POST'])]
|
||||
public function logout(): void
|
||||
{
|
||||
// Intercepted by the security firewall — never executed.
|
||||
}
|
||||
|
||||
#[Route('/register', name: 'MineSeekerBundle_register')]
|
||||
public function register(
|
||||
Request $request,
|
||||
UserPasswordHasherInterface $hasher,
|
||||
EntityManagerInterface $em,
|
||||
MailerInterface $mailer,
|
||||
): Response {
|
||||
if ($this->getUser()) {
|
||||
return $this->redirectToRoute('MineSeekerBundle_homepage');
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$username = trim((string) $request->request->get('_username', ''));
|
||||
$email = trim((string) $request->request->get('_email', ''));
|
||||
$password = (string) $request->request->get('_password', '');
|
||||
$passwordConfirm = (string) $request->request->get('_password_confirm', '');
|
||||
|
||||
if (mb_strlen($username) < 3) {
|
||||
$errors['username'] = 'Username must be at least 3 characters.';
|
||||
} elseif ($em->getRepository(User::class)->findOneBy(['username' => $username])) {
|
||||
$errors['username'] = 'This username is already taken.';
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors['email'] = 'Please enter a valid email address.';
|
||||
} elseif ($em->getRepository(User::class)->findOneBy(['email' => $email])) {
|
||||
$errors['email'] = 'This email address is already registered.';
|
||||
}
|
||||
|
||||
if (mb_strlen($password) < 6) {
|
||||
$errors['password'] = 'Password must be at least 6 characters.';
|
||||
} elseif ($password !== $passwordConfirm) {
|
||||
$errors['password_confirm'] = 'Passwords do not match.';
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
$token = bin2hex(random_bytes(32));
|
||||
|
||||
$user = (new User())
|
||||
->setUsername($username)
|
||||
->setEmail($email)
|
||||
->setIsVerified(false)
|
||||
->setVerificationToken($token);
|
||||
|
||||
$user->setPassword($hasher->hashPassword($user, $password));
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
$activationUrl = $this->generateUrl(
|
||||
'MineSeekerBundle_activate',
|
||||
['token' => $token],
|
||||
UrlGeneratorInterface::ABSOLUTE_URL,
|
||||
);
|
||||
|
||||
$mailer->send(
|
||||
(new TemplatedEmail())
|
||||
->from('noreply@mineseeker.ninja')
|
||||
->to($email)
|
||||
->subject('Activate your MineSeeker account')
|
||||
->htmlTemplate('emails/activation.html.twig')
|
||||
->context([
|
||||
'username' => $username,
|
||||
'activation_url' => $activationUrl,
|
||||
])
|
||||
);
|
||||
|
||||
$this->addFlash('verify_email', $email);
|
||||
|
||||
return $this->redirectToRoute('MineSeekerBundle_register');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('Security/register.html.twig', [
|
||||
'errors' => $errors,
|
||||
'last_username' => $request->request->get('_username', ''),
|
||||
'last_email' => $request->request->get('_email', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/activate/{token}', name: 'MineSeekerBundle_activate')]
|
||||
public function activate(string $token, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->findOneBy(['verificationToken' => $token]);
|
||||
|
||||
if (!$user) {
|
||||
$this->addFlash('error', 'This activation link is invalid or has already been used.');
|
||||
return $this->redirectToRoute('MineSeekerBundle_login');
|
||||
}
|
||||
|
||||
$user->setIsVerified(true)->setVerificationToken(null);
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', 'Your account is now active. Welcome, ' . $user->getUsername() . '!');
|
||||
|
||||
return $this->redirectToRoute('MineSeekerBundle_login');
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,10 @@ use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Class User
|
||||
@@ -28,6 +30,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 09.
|
||||
*/
|
||||
#[Table(name: 'app_user')]
|
||||
#[Entity(repositoryClass: UserRepository::class)]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
@@ -43,6 +46,15 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[Column(nullable: true)]
|
||||
private ?string $password = null;
|
||||
|
||||
#[Column(length: 254, unique: true, nullable: true)]
|
||||
private ?string $email = null;
|
||||
|
||||
#[Column]
|
||||
private bool $isVerified = false;
|
||||
|
||||
#[Column(length: 64, nullable: true)]
|
||||
private ?string $verificationToken = null;
|
||||
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
@@ -92,4 +104,37 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(?string $email): self
|
||||
{
|
||||
$this->email = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isVerified(): bool
|
||||
{
|
||||
return $this->isVerified;
|
||||
}
|
||||
|
||||
public function setIsVerified(bool $isVerified): self
|
||||
{
|
||||
$this->isVerified = $isVerified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVerificationToken(): ?string
|
||||
{
|
||||
return $this->verificationToken;
|
||||
}
|
||||
|
||||
public function setVerificationToken(?string $verificationToken): self
|
||||
{
|
||||
$this->verificationToken = $verificationToken;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
87
src/Migrations/2026/04/Version20260411180138.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20260411180138
|
||||
*
|
||||
* @package App\Migrations
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 11.
|
||||
*/
|
||||
final class Version20260411180138 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Initialize the Mineseeker';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE SEQUENCE app_user_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE gamer_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE grid_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE grid_row_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE played_game_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE step_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE app_user (id INT NOT NULL, username VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) DEFAULT NULL, email VARCHAR(254) DEFAULT NULL, is_verified BOOLEAN NOT NULL, verification_token VARCHAR(64) DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_88BDF3E9F85E0677 ON app_user (username)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_88BDF3E9E7927C74 ON app_user (email)');
|
||||
$this->addSql('CREATE TABLE gamer (id INT NOT NULL, user_name VARCHAR(100) NOT NULL, ip VARCHAR(20) DEFAULT NULL, country VARCHAR(100) DEFAULT NULL, user_agent VARCHAR(255) DEFAULT NULL, conn_timestamp TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE TABLE grid (id INT NOT NULL, played_game_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_2E20D9375AA11DBB ON grid (played_game_id)');
|
||||
$this->addSql('CREATE TABLE grid_row (id INT NOT NULL, grid INT DEFAULT NULL, grid_col JSON NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_6FAD08EB2E20D937 ON grid_row (grid)');
|
||||
$this->addSql('CREATE TABLE played_game (id INT NOT NULL, red_id INT DEFAULT NULL, red_anon INT DEFAULT NULL, blue_id INT DEFAULT NULL, blue_anon INT DEFAULT NULL, game_assoc VARCHAR(50) NOT NULL, red_points INT DEFAULT NULL, blue_points INT DEFAULT NULL, red_exploded_bomb BOOLEAN DEFAULT NULL, blue_exploded_bomb BOOLEAN DEFAULT NULL, resign VARCHAR(7) DEFAULT NULL, created TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_54BE80398BBE8922 ON played_game (red_id)');
|
||||
$this->addSql('CREATE INDEX IDX_54BE8039F24372EB ON played_game (red_anon)');
|
||||
$this->addSql('CREATE INDEX IDX_54BE80395AB9393F ON played_game (blue_id)');
|
||||
$this->addSql('CREATE INDEX IDX_54BE8039C64E7C7C ON played_game (blue_anon)');
|
||||
$this->addSql('CREATE TABLE step (id INT NOT NULL, played_game_id INT DEFAULT NULL, row INT NOT NULL, col INT NOT NULL, w_bomb BOOLEAN DEFAULT NULL, player VARCHAR(10) DEFAULT NULL, revealed_cells JSON DEFAULT NULL, created TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_43B9FE3C5AA11DBB ON step (played_game_id)');
|
||||
$this->addSql('ALTER TABLE grid ADD CONSTRAINT FK_2E20D9375AA11DBB FOREIGN KEY (played_game_id) REFERENCES played_game (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE grid_row ADD CONSTRAINT FK_6FAD08EB2E20D937 FOREIGN KEY (grid) REFERENCES grid (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE played_game ADD CONSTRAINT FK_54BE80398BBE8922 FOREIGN KEY (red_id) REFERENCES app_user (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE played_game ADD CONSTRAINT FK_54BE8039F24372EB FOREIGN KEY (red_anon) REFERENCES gamer (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE played_game ADD CONSTRAINT FK_54BE80395AB9393F FOREIGN KEY (blue_id) REFERENCES app_user (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE played_game ADD CONSTRAINT FK_54BE8039C64E7C7C FOREIGN KEY (blue_anon) REFERENCES gamer (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE step ADD CONSTRAINT FK_43B9FE3C5AA11DBB FOREIGN KEY (played_game_id) REFERENCES played_game (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP SEQUENCE app_user_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE gamer_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE grid_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE grid_row_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE played_game_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE step_id_seq CASCADE');
|
||||
$this->addSql('ALTER TABLE grid DROP CONSTRAINT FK_2E20D9375AA11DBB');
|
||||
$this->addSql('ALTER TABLE grid_row DROP CONSTRAINT FK_6FAD08EB2E20D937');
|
||||
$this->addSql('ALTER TABLE played_game DROP CONSTRAINT FK_54BE80398BBE8922');
|
||||
$this->addSql('ALTER TABLE played_game DROP CONSTRAINT FK_54BE8039F24372EB');
|
||||
$this->addSql('ALTER TABLE played_game DROP CONSTRAINT FK_54BE80395AB9393F');
|
||||
$this->addSql('ALTER TABLE played_game DROP CONSTRAINT FK_54BE8039C64E7C7C');
|
||||
$this->addSql('ALTER TABLE step DROP CONSTRAINT FK_43B9FE3C5AA11DBB');
|
||||
$this->addSql('DROP TABLE app_user');
|
||||
$this->addSql('DROP TABLE gamer');
|
||||
$this->addSql('DROP TABLE grid');
|
||||
$this->addSql('DROP TABLE grid_row');
|
||||
$this->addSql('DROP TABLE played_game');
|
||||
$this->addSql('DROP TABLE step');
|
||||
}
|
||||
}
|
||||
44
src/Security/UserChecker.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Class UserChecker
|
||||
*
|
||||
* @package App\Security
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 11.
|
||||
*/
|
||||
final class UserChecker implements UserCheckerInterface
|
||||
{
|
||||
public function checkPreAuth(UserInterface $user): void
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$user->isVerified()) {
|
||||
throw new CustomUserMessageAuthenticationException(
|
||||
'Please verify your email address before signing in. Check your inbox for the activation link.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPostAuth(UserInterface $user): void { }
|
||||
}
|
||||
@@ -15,6 +15,28 @@
|
||||
<section
|
||||
class="hero{% if app.request.attributes.get('_route') != 'MineSeekerBundle_homepage' %} hero--compact{% endif %}">
|
||||
|
||||
<div class="hero-auth">
|
||||
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
|
||||
<a href="{{ path('MineSeekerBundle_profile') }}" class="hero-auth-btn hero-auth-btn--profile">
|
||||
<i class="fa fa-user-circle"></i>
|
||||
{{ app.user.username }}
|
||||
</a>
|
||||
<form method="post" action="{{ path('MineSeekerBundle_logout') }}">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('logout') }}"/>
|
||||
<button type="submit" class="hero-auth-btn hero-auth-btn--out">
|
||||
<i class="fa fa-sign-out"></i> Sign out
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a href="{{ path('MineSeekerBundle_login') }}" class="hero-auth-btn">
|
||||
<i class="fa fa-sign-in"></i> Sign in
|
||||
</a>
|
||||
<a href="{{ path('MineSeekerBundle_register') }}" class="hero-auth-btn hero-auth-btn--register">
|
||||
<i class="fa fa-user-plus"></i> Register
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<a class="hero-logo" href="{{ path('MineSeekerBundle_homepage') }}">
|
||||
<img src="{{ asset('images/mine-logo-txt.png') }}" alt="MineSeeker"/>
|
||||
</a>
|
||||
@@ -22,12 +44,7 @@
|
||||
<div class="hero-body">
|
||||
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
|
||||
<p class="hero-sub">
|
||||
{% if app.user.facebookId is defined and app.user.facebookId is not null %}
|
||||
<img class="hero-avatar"
|
||||
src="http://graph.facebook.com/{{ app.user.facebookId }}/picture?type=square&width=80&height=80"
|
||||
alt=""/>
|
||||
{% endif %}
|
||||
Welcome back, <strong>{{ app.user.realName is not null ? app.user.realName : app.user.username }}</strong>
|
||||
Welcome back, <strong>{{ app.user.username }}</strong>
|
||||
</p>
|
||||
<h1>Ready for another round?</h1>
|
||||
{% else %}
|
||||
@@ -41,17 +58,82 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<section class="feature-block">
|
||||
<div class="feature-block__inner">
|
||||
<div class="feature-block__visual feature-block__visual--stats">
|
||||
<i class="fa fa-bar-chart"></i>
|
||||
<i class="fa fa-trophy"></i>
|
||||
<i class="fa fa-history"></i>
|
||||
</div>
|
||||
<div class="feature-block__text">
|
||||
<p class="feature-block__label">For registered players</p>
|
||||
<h2 class="feature-block__title">Track your legacy</h2>
|
||||
<p class="feature-block__body">
|
||||
Create a free account and every game you play gets recorded.
|
||||
Watch your win rate climb, revisit past battles, and prove your dominance
|
||||
on the board — one detonation at a time.
|
||||
</p>
|
||||
{% if not is_granted("IS_AUTHENTICATED_REMEMBERED") %}
|
||||
<a href="{{ path('MineSeekerBundle_register') }}" class="feature-block__cta">
|
||||
<i class="fa fa-user-plus"></i> Create free account
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="feature-block feature-block--reverse feature-block--msn">
|
||||
<div class="feature-block__inner">
|
||||
<div class="feature-block__visual feature-block__visual--msn">
|
||||
<img src="{{ asset('images/msn-logo.png') }}" alt="MSN Messenger" class="msn-logo"/>
|
||||
<img src="{{ asset('images/msn-minesweeper.png') }}" alt="MSN Messenger Minesweeper" class="msn-screenshot"/>
|
||||
</div>
|
||||
<div class="feature-block__text">
|
||||
<p class="feature-block__label">Our inspiration</p>
|
||||
<h2 class="feature-block__title">Born from a legend</h2>
|
||||
<p class="feature-block__body">
|
||||
Remember the minesweeper hidden inside Microsoft's MSN Messenger?
|
||||
That tiny two-player gem sparked countless friendships and rivalries
|
||||
in the early 2000s. We loved it — so we rebuilt it for today.
|
||||
Real-time, multiplayer, and no MSN account required.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="tech-section">
|
||||
<p class="tech-label">Built with</p>
|
||||
<div class="tech-logos">
|
||||
<img src="{{ asset('images/technologies/symfony.svg') }}" alt="Symfony"/>
|
||||
<img src="{{ asset('images/technologies/howler.svg') }}" alt="Howler.js"/>
|
||||
<img src="{{ asset('images/technologies/tanstack-query.svg') }}" alt="TanStack Query"/>
|
||||
<img src="{{ asset('images/technologies/mercure.svg') }}" alt="Mercure"/>
|
||||
<img src="{{ asset('images/technologies/vite.svg') }}" alt="Vite"/>
|
||||
<img src="{{ asset('images/technologies/bun.svg') }}" alt="Bun"/>
|
||||
<img src="{{ asset('images/technologies/phpstorm.svg') }}" alt="PHPStorm"/>
|
||||
<a href="https://symfony.com" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/symfony.svg') }}" alt="Symfony"/>
|
||||
</a>
|
||||
<a href="https://howlerjs.com" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/howler.svg') }}" alt="Howler.js"/>
|
||||
</a>
|
||||
<a href="https://tanstack.com/query" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/tanstack-query.svg') }}" alt="TanStack Query"/>
|
||||
</a>
|
||||
<a href="https://mercure.rocks" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/mercure.svg') }}" alt="Mercure"/>
|
||||
</a>
|
||||
<a href="https://vitejs.dev" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/vite.svg') }}" alt="Vite"/>
|
||||
</a>
|
||||
<a href="https://bun.sh" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/bun.svg') }}" alt="Bun"/>
|
||||
</a>
|
||||
<a href="https://www.jetbrains.com/phpstorm" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/phpstorm.svg') }}" alt="PHPStorm"/>
|
||||
</a>
|
||||
<a href="https://archlinux.org" target="_blank" rel="noopener" class="tech-link">
|
||||
<img src="{{ asset('images/technologies/archlinux.svg') }}" alt="Arch Linux"/>
|
||||
</a>
|
||||
</div>
|
||||
<p class="tech-oss">
|
||||
<i class="fa fa-heart"></i>
|
||||
This game would not exist without the incredible open-source community. <br>
|
||||
Thank you to every contributor, maintainer, and creator behind these projects.
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
83
templates/Security/login.html.twig
Normal file
@@ -0,0 +1,83 @@
|
||||
{% extends 'Game/index.html.twig' %}
|
||||
|
||||
{% block title %} - Sign In{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="auth-page">
|
||||
|
||||
{% for message in app.flashes('success') %}
|
||||
<div class="auth-flash auth-flash--success">
|
||||
<i class="fa fa-check-circle"></i> {{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for message in app.flashes('error') %}
|
||||
<div class="auth-flash auth-flash--error">
|
||||
<i class="fa fa-exclamation-triangle"></i> {{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="auth-card">
|
||||
<h2 class="auth-title">Sign In</h2>
|
||||
<p class="auth-sub">Welcome back, commander</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="auth-error">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
{{ error.messageKey|trans(error.messageData, 'security') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="auth-form" method="post" action="{{ path('MineSeekerBundle_login') }}">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}"/>
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="username" class="auth-label">Username</label>
|
||||
<div class="auth-input-wrap">
|
||||
<i class="fa fa-user auth-input-icon"></i>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="_username"
|
||||
class="auth-input"
|
||||
value="{{ last_username }}"
|
||||
autocomplete="username"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="password" class="auth-label">Password</label>
|
||||
<div class="auth-input-wrap">
|
||||
<i class="fa fa-lock auth-input-icon"></i>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="_password"
|
||||
class="auth-input"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="auth-remember">
|
||||
<input type="checkbox" name="_remember_me"/>
|
||||
<span>Remember me</span>
|
||||
</label>
|
||||
|
||||
<button type="submit" class="auth-submit">
|
||||
<i class="fa fa-sign-in"></i> Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-switch">
|
||||
No account yet?
|
||||
<a href="{{ path('MineSeekerBundle_register') }}">Create one</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
110
templates/Security/profile.html.twig
Normal file
@@ -0,0 +1,110 @@
|
||||
{% extends 'Game/index.html.twig' %}
|
||||
|
||||
{% block title %} - Profile{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="profile-page">
|
||||
|
||||
<div class="profile-header">
|
||||
<div class="profile-avatar">
|
||||
{{ app.user.username|slice(0, 2)|upper }}
|
||||
</div>
|
||||
<div class="profile-info">
|
||||
<h1 class="profile-name">{{ app.user.username }}</h1>
|
||||
{% if app.user.email %}
|
||||
<p class="profile-email">
|
||||
<i class="fa fa-envelope"></i>
|
||||
{{ app.user.email }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="profile-role">
|
||||
<i class="fa fa-shield"></i> Registered commander
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-stats">
|
||||
<div class="profile-stat">
|
||||
<i class="fa fa-gamepad profile-stat__icon"></i>
|
||||
<span class="profile-stat__value">{{ stats.total }}</span>
|
||||
<span class="profile-stat__label">Games played</span>
|
||||
</div>
|
||||
<div class="profile-stat profile-stat--win">
|
||||
<i class="fa fa-trophy profile-stat__icon"></i>
|
||||
<span class="profile-stat__value">{{ stats.wins }}</span>
|
||||
<span class="profile-stat__label">Victories</span>
|
||||
</div>
|
||||
<div class="profile-stat profile-stat--loss">
|
||||
<i class="fa fa-flag profile-stat__icon"></i>
|
||||
<span class="profile-stat__value">{{ stats.losses }}</span>
|
||||
<span class="profile-stat__label">Defeats</span>
|
||||
</div>
|
||||
<div class="profile-stat profile-stat--bomb">
|
||||
<i class="fa fa-bomb profile-stat__icon"></i>
|
||||
<span class="profile-stat__value">{{ stats.bombs }}</span>
|
||||
<span class="profile-stat__label">Mines hit</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if recent|length > 0 %}
|
||||
<div class="profile-section">
|
||||
<h2 class="profile-section__title">
|
||||
<i class="fa fa-history"></i> Recent battles
|
||||
</h2>
|
||||
<div class="profile-games">
|
||||
{% for game in recent %}
|
||||
{% set is_red = game.red and game.red.id == app.user.id %}
|
||||
{% set my_points = is_red ? game.redPoints : game.bluePoints %}
|
||||
{% set opp_points = is_red ? game.bluePoints : game.redPoints %}
|
||||
{% set opp = is_red ? game.blue : game.red %}
|
||||
{% set opp_anon = is_red ? game.blueAnon : game.redAnon %}
|
||||
|
||||
{% set result = 'draw' %}
|
||||
{% if game.resign == (is_red ? 'red' : 'blue') %}
|
||||
{% set result = 'loss' %}
|
||||
{% elseif game.resign == (is_red ? 'blue' : 'red') %}
|
||||
{% set result = 'win' %}
|
||||
{% elseif my_points is not null and opp_points is not null %}
|
||||
{% if my_points > opp_points %}
|
||||
{% set result = 'win' %}
|
||||
{% elseif my_points < opp_points %}
|
||||
{% set result = 'loss' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="profile-game profile-game--{{ result }}">
|
||||
<span class="profile-game__badge">
|
||||
{{ result == 'win' ? 'W' : (result == 'loss' ? 'L' : 'D') }}
|
||||
</span>
|
||||
<span class="profile-game__score">
|
||||
{{ my_points ?? '—' }} : {{ opp_points ?? '—' }}
|
||||
</span>
|
||||
<span class="profile-game__vs">vs</span>
|
||||
<span class="profile-game__opponent">
|
||||
{% if opp %}
|
||||
{{ opp.username }}
|
||||
{% elseif opp_anon %}
|
||||
{{ opp_anon.userName }}
|
||||
{% else %}
|
||||
Guest
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="profile-game__color">
|
||||
<i class="fa fa-circle" style="color: {{ is_red ? '#c0392b' : '#2980b9' }}"></i>
|
||||
</span>
|
||||
<span class="profile-game__date">
|
||||
{{ game.updated ? game.updated|date('Y-m-d') : '' }}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="profile-empty">
|
||||
<i class="fa fa-inbox"></i>
|
||||
<p>No games recorded yet. <a href="{{ path('MineSeekerBundle_gamePlay') }}">Start playing!</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
120
templates/Security/register.html.twig
Normal file
@@ -0,0 +1,120 @@
|
||||
{% extends 'Game/index.html.twig' %}
|
||||
|
||||
{% block title %} - Register{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="auth-page">
|
||||
|
||||
{% for email in app.flashes('verify_email') %}
|
||||
<div class="auth-card auth-card--sent">
|
||||
<div class="auth-sent-icon"><i class="fa fa-envelope-o"></i></div>
|
||||
<h2 class="auth-title">Check your inbox</h2>
|
||||
<p class="auth-sub">We sent an activation link to</p>
|
||||
<p class="auth-sent-email">{{ email }}</p>
|
||||
<p class="auth-sent-note">
|
||||
Click the link in the email to activate your account.<br>
|
||||
The link expires in <strong>24 hours</strong>.
|
||||
</p>
|
||||
<a href="{{ path('MineSeekerBundle_login') }}" class="auth-submit" style="text-decoration:none; margin-top:16px;">
|
||||
Go to Sign In
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="auth-card">
|
||||
<h2 class="auth-title">Create Account</h2>
|
||||
<p class="auth-sub">Join the battle — no subscription required</p>
|
||||
|
||||
<form class="auth-form" method="post" action="{{ path('MineSeekerBundle_register') }}">
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="username" class="auth-label">Username</label>
|
||||
<div class="auth-input-wrap">
|
||||
<i class="fa fa-user auth-input-icon"></i>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="_username"
|
||||
class="auth-input{% if errors.username is defined %} auth-input--error{% endif %}"
|
||||
value="{{ last_username }}"
|
||||
autocomplete="username"
|
||||
autofocus
|
||||
required
|
||||
minlength="3"
|
||||
/>
|
||||
</div>
|
||||
{% if errors.username is defined %}
|
||||
<p class="auth-field-error"><i class="fa fa-exclamation-circle"></i> {{ errors.username }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="email" class="auth-label">Email</label>
|
||||
<div class="auth-input-wrap">
|
||||
<i class="fa fa-envelope auth-input-icon"></i>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="_email"
|
||||
class="auth-input{% if errors.email is defined %} auth-input--error{% endif %}"
|
||||
value="{{ last_email }}"
|
||||
autocomplete="email"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{% if errors.email is defined %}
|
||||
<p class="auth-field-error"><i class="fa fa-exclamation-circle"></i> {{ errors.email }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="password" class="auth-label">Password</label>
|
||||
<div class="auth-input-wrap">
|
||||
<i class="fa fa-lock auth-input-icon"></i>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="_password"
|
||||
class="auth-input{% if errors.password is defined %} auth-input--error{% endif %}"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
minlength="6"
|
||||
/>
|
||||
</div>
|
||||
{% if errors.password is defined %}
|
||||
<p class="auth-field-error"><i class="fa fa-exclamation-circle"></i> {{ errors.password }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="password_confirm" class="auth-label">Confirm Password</label>
|
||||
<div class="auth-input-wrap">
|
||||
<i class="fa fa-lock auth-input-icon"></i>
|
||||
<input
|
||||
type="password"
|
||||
id="password_confirm"
|
||||
name="_password_confirm"
|
||||
class="auth-input{% if errors.password_confirm is defined %} auth-input--error{% endif %}"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{% if errors.password_confirm is defined %}
|
||||
<p class="auth-field-error"><i class="fa fa-exclamation-circle"></i> {{ errors.password_confirm }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="auth-submit">
|
||||
<i class="fa fa-user-plus"></i> Create Account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-switch">
|
||||
Already have an account?
|
||||
<a href="{{ path('MineSeekerBundle_login') }}">Sign in</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,39 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-control" content="max-age=1209600;public">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="keywords" content="game,mineseeker,mine,seeker,laszlolang.com">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="revisit-after" content="2 days">
|
||||
<meta name="resource-type" content="document">
|
||||
<meta name="country" content="Hungary">
|
||||
<meta name="description" content="This is a new minesweeper, multiplayer game.">
|
||||
<meta name="content-language" content="hu,hun,hungarian">
|
||||
{% block metas %}{% endblock %}
|
||||
<title>MineSeeker{% block title %}{% endblock %}</title>
|
||||
{% block stylesheets %}{% endblock %}
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-control" content="max-age=1209600;public">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="keywords" content="game,mineseeker,mine,seeker,laszlolang.com">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="revisit-after" content="2 days">
|
||||
<meta name="resource-type" content="document">
|
||||
<meta name="country" content="Hungary">
|
||||
<meta name="description" content="This is a new minesweeper, multiplayer game.">
|
||||
<meta name="content-language" content="hu,hun,hungarian">
|
||||
{% block metas %}{% endblock %}
|
||||
<title>MineSeeker{% block title %}{% endblock %}</title>
|
||||
{% block stylesheets %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img src="{{ asset('/images/beta-logo-png-2.png') }}" alt="Beta version" class="mine-beta">
|
||||
{% block bodyTop %}{% endblock %}
|
||||
|
||||
{% block bodyTop %}{% endblock %}
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
<main>
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
{% block footer %}{% endblock %}
|
||||
</footer>
|
||||
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
<main>
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
{% block footer %}{% endblock %}
|
||||
</footer>
|
||||
|
||||
{% block javascripts %}{% endblock %}
|
||||
{% block javascripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
103
templates/emails/activation.html.twig
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Activate your MineSeeker account</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0; padding: 0;
|
||||
background: #07090d;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
}
|
||||
.wrapper {
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.logo {
|
||||
text-align: center;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
.logo img {
|
||||
width: 200px;
|
||||
}
|
||||
.card {
|
||||
background: #0f1923;
|
||||
border: 1px solid rgba(35, 111, 135, 0.25);
|
||||
border-radius: 10px;
|
||||
padding: 40px 36px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
margin: 0 0 10px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.sub {
|
||||
font-size: 14px;
|
||||
color: rgba(149, 207, 245, 0.7);
|
||||
margin: 0 0 28px;
|
||||
}
|
||||
p {
|
||||
font-size: 15px;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
line-height: 1.7;
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background: linear-gradient(to bottom, #ad0a05, #f67d52);
|
||||
color: #ffffff !important;
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
padding: 16px 48px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.fallback {
|
||||
margin-top: 24px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
word-break: break-all;
|
||||
}
|
||||
.fallback a { color: rgba(149, 207, 245, 0.5); }
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 32px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="logo">
|
||||
<img src="{{ absolute_url(asset('images/mine-logo-txt.png')) }}" alt="MineSeeker"/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>One step to go</h1>
|
||||
<p class="sub">Activate your account, commander</p>
|
||||
<p>
|
||||
Hi <strong>{{ username }}</strong>,<br>
|
||||
Thanks for registering on MineSeeker. Click the button below to verify your
|
||||
email address and activate your account.
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn" href="{{ activation_url }}">Activate account</a>
|
||||
</p>
|
||||
<p class="fallback">
|
||||
If the button doesn't work, copy and paste this link into your browser:<br>
|
||||
<a href="{{ activation_url }}">{{ activation_url }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© {{ "now"|date("Y") }} MineSeeker • This link expires in 24 hours.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||