Compare commits

...

8 Commits

36 changed files with 2075 additions and 377 deletions
+5
View File
@@ -40,3 +40,8 @@ desktop.ini
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
###> symfony/asset-mapper ###
/public/assets/
/assets/vendor/
###< symfony/asset-mapper ###
+1
View File
@@ -6,6 +6,7 @@
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/var" />
<excludeFolder url="file://$MODULE_DIR$/vendor" />
<excludeFolder url="file://$MODULE_DIR$/public/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpDockerContainerSettings">
<list>
<map>
<entry key="0bef9119-879f-4b48-9f5f-1234513849a3">
<value>
<DockerContainerSettings>
<option name="version" value="1" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/opt/project" />
<option name="hostPath" value="$PROJECT_DIR$" />
</DockerVolumeBindingImpl>
</list>
</option>
</DockerContainerSettings>
</value>
</entry>
</map>
</list>
</component>
</project>
Generated
+6
View File
@@ -59,6 +59,12 @@
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/webpack-encore-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/asset-mapper" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
<path value="$PROJECT_DIR$/vendor/psr/link" />
<path value="$PROJECT_DIR$/vendor/symfony/web-link" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Symfony2PluginSettings">
<option name="pluginEnabled" value="true" />
<option name="profilerCsvPath" value="" />
</component>
</project>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
</project>
+2
View File
@@ -5,5 +5,7 @@
* (and its CSS file) in your base layout (base.html.twig).
*/
require('bootstrap');
// any CSS you import will output into a single css file (app.scss in this case)
import './styles/app.scss';
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+594 -2
View File
@@ -1,3 +1,595 @@
body {
background-color: lightgray;
@use 'app/nav';
@use 'app/footer';
// =============================================================================
// Fonts
// =============================================================================
@font-face {
font-family: 'Agave';
src: url('../fonts/agave-regular.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Fantasque Sans Mono';
src: url('../fonts/fantasque-sans-mono-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('../fonts/jetbrains-mono-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
src: url('../fonts/material-symbols-outlined.woff2') format('woff');
}
// =============================================================================
// Design Tokens — Light Palette
// =============================================================================
$lt-surface: #f7f9fb;
$lt-surface-dim: #d8dadc;
$lt-surface-bright: #f7f9fb;
$lt-surface-container-lowest: #ffffff;
$lt-surface-container-low: #f2f4f6;
$lt-surface-container: #eceef0;
$lt-surface-container-high: #e6e8ea;
$lt-surface-container-highest: #e0e3e5;
$lt-on-surface: #191c1e;
$lt-on-surface-variant: #404751;
$lt-outline: #707883;
$lt-outline-variant: #c0c7d3;
$lt-surface-tint: #0061a2;
$lt-surface-variant: #e0e3e5;
$lt-primary: #0061a2;
$lt-on-primary: #ffffff;
$lt-primary-container: #50a7fa;
$lt-on-primary-container: #003b65;
$lt-inverse-primary: #9dcaff;
$lt-secondary: #565e74;
$lt-on-secondary: #ffffff;
$lt-secondary-container: #dae2fd;
$lt-on-secondary-container: #5c647a;
$lt-tertiary: #505f76;
$lt-on-tertiary: #ffffff;
$lt-tertiary-container: #94a4bc;
$lt-on-tertiary-container: #2a3a4f;
$lt-error: #ba1a1a;
$lt-on-error: #ffffff;
$lt-error-container: #ffdad6;
$lt-on-error-container: #93000a;
$lt-background: #f7f9fb;
$lt-on-background: #191c1e;
// =============================================================================
// Design Tokens — Dark Palette
// =============================================================================
$dk-surface: #0b1326;
$dk-surface-dim: #0b1326;
$dk-surface-bright: #31394d;
$dk-surface-container-lowest: #060e20;
$dk-surface-container-low: #131b2e;
$dk-surface-container: #171f33;
$dk-surface-container-high: #222a3d;
$dk-surface-container-highest: #2d3449;
$dk-on-surface: #dae2fd;
$dk-on-surface-variant: #c0c7d3;
$dk-inverse-surface: #dae2fd;
$dk-inverse-on-surface: #283044;
$dk-outline: #8a919d;
$dk-outline-variant: #404751;
$dk-surface-tint: #9dcaff;
$dk-surface-variant: #2d3449;
$dk-primary: #9dcaff;
$dk-on-primary: #003257;
$dk-primary-container: #50a7fa;
$dk-on-primary-container: #003b65;
$dk-inverse-primary: #0061a2;
$dk-secondary: #89ceff;
$dk-on-secondary: #00344d;
$dk-secondary-container: #00a2e6;
$dk-on-secondary-container: #00344e;
$dk-tertiary: #c0c1ff;
$dk-on-tertiary: #1000a9;
$dk-tertiary-container: #9598ff;
$dk-on-tertiary-container: #1c16b1;
$dk-error: #ffb4ab;
$dk-on-error: #690005;
$dk-error-container: #93000a;
$dk-on-error-container: #ffdad6;
$dk-background: #0b1326;
$dk-on-background: #dae2fd;
// =============================================================================
// Shared Tokens — Typography & Radius
// =============================================================================
$font-display: 'Agave', system-ui, sans-serif;
$font-body: 'Fantasque Sans Mono', system-ui, -apple-system, sans-serif;
$font-mono: 'JetBrains Mono', 'Courier New', monospace;
$radius-sm: 0.25rem;
$radius-default: 0.5rem;
$radius-md: 0.75rem;
$radius-lg: 1rem;
$radius-xl: 1.5rem;
$radius-full: 9999px;
// =============================================================================
// Bootstrap Variable Overrides
// Light mode values are the compiled baseline.
// $color-mode-type: media-query makes Bootstrap emit its own dark-mode block
// inside @media (prefers-color-scheme: dark) automatically.
// =============================================================================
$enable-dark-mode: true;
$color-mode-type: media-query; // use prefers-color-scheme instead of data-bs-theme
// Semantic colors — light baseline
$primary: $lt-primary-container; // #50a7fa — Electric Azure anchor
$secondary: $lt-secondary; // #565e74
$success: #16a34a;
$info: $lt-primary; // #0061a2
$warning: #d97706;
$danger: $lt-error; // #ba1a1a
$light: $lt-surface-container-low; // #f2f4f6
$dark: $lt-on-surface; // #191c1e
// Body — light defaults
$body-bg: $lt-background; // #f7f9fb
$body-color: $lt-on-surface; // #191c1e
$body-secondary-color: $lt-on-surface-variant; // #404751
// Links
$link-color: $lt-primary; // #0061a2
$link-hover-color: $lt-primary-container; // #50a7fa
// Borders
$border-color: $lt-outline-variant; // #c0c7d3
// Border radius
$border-radius: $radius-default;
$border-radius-sm: $radius-sm;
$border-radius-lg: $radius-md;
$border-radius-xl: $radius-lg;
$border-radius-xxl: $radius-xl;
$border-radius-pill: $radius-full;
// Typography
$font-family-sans-serif: $font-body;
$font-family-monospace: $font-mono;
$font-size-base: 1rem;
$line-height-base: 1.5;
// Headings — Bootstrap uses var(--bs-heading-color), dark mode auto-switches
$headings-font-family: $font-display;
$headings-font-weight: 600;
$headings-line-height: 1.3;
$headings-color: $lt-on-surface; // #191c1e
// Grid
$grid-gutter-width: 1.5rem; // 24px
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1200px,
);
// Code
$code-color: $lt-tertiary; // #505f76
// Bootstrap dark mode variables — used inside Bootstrap's generated media query
$body-color-dark: $dk-on-surface; // #dae2fd
$body-bg-dark: $dk-background; // #0b1326
$body-secondary-color-dark: $dk-on-surface-variant; // #c0c7d3
$border-color-dark: $dk-outline-variant; // #404751
$link-color-dark: $dk-primary; // #9dcaff
$headings-color-dark: $dk-on-surface; // #dae2fd
$code-color-dark: $dk-tertiary; // #c0c1ff
// =============================================================================
// Bootstrap Import
// Bootstrap 5.x uses @import internally — @use with() is not supported.
// =============================================================================
@import '~bootstrap/scss/bootstrap';
// =============================================================================
// CSS Custom Properties — Light Mode (default)
// =============================================================================
:root {
// Surface
--color-surface: #{$lt-surface};
--color-surface-dim: #{$lt-surface-dim};
--color-surface-bright: #{$lt-surface-bright};
--color-surface-container-lowest: #{$lt-surface-container-lowest};
--color-surface-container-low: #{$lt-surface-container-low};
--color-surface-container: #{$lt-surface-container};
--color-surface-container-high: #{$lt-surface-container-high};
--color-surface-container-highest: #{$lt-surface-container-highest};
--color-on-surface: #{$lt-on-surface};
--color-on-surface-variant: #{$lt-on-surface-variant};
--color-outline: #{$lt-outline};
--color-outline-variant: #{$lt-outline-variant};
--color-surface-tint: #{$lt-surface-tint};
--color-surface-variant: #{$lt-surface-variant};
// RGB channels for rgba() composition (navbar glass, overlays)
--color-surface-container-lowest-rgb: 255, 255, 255;
// Primary
--color-primary: #{$lt-primary};
--color-on-primary: #{$lt-on-primary};
--color-primary-container: #{$lt-primary-container};
--color-on-primary-container: #{$lt-on-primary-container};
--color-inverse-primary: #{$lt-inverse-primary};
// Secondary
--color-secondary: #{$lt-secondary};
--color-on-secondary: #{$lt-on-secondary};
--color-secondary-container: #{$lt-secondary-container};
--color-on-secondary-container: #{$lt-on-secondary-container};
// Tertiary
--color-tertiary: #{$lt-tertiary};
--color-on-tertiary: #{$lt-on-tertiary};
--color-tertiary-container: #{$lt-tertiary-container};
--color-on-tertiary-container: #{$lt-on-tertiary-container};
// Error
--color-error: #{$lt-error};
--color-on-error: #{$lt-on-error};
--color-error-container: #{$lt-error-container};
--color-on-error-container: #{$lt-on-error-container};
// Background
--color-background: #{$lt-background};
--color-on-background: #{$lt-on-background};
// Font stacks
--font-display: #{$font-display};
--font-body: #{$font-body};
--font-mono: #{$font-mono};
// Spacing (8px base from light design doc)
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-section: 120px;
--spacing-section-mobile: 64px;
// Border radius
--radius-sm: #{$radius-sm};
--radius: #{$radius-default};
--radius-md: #{$radius-md};
--radius-lg: #{$radius-lg};
--radius-xl: #{$radius-xl};
--radius-full: #{$radius-full};
// Elevation
--shadow-card-hover: 0px 4px 20px rgba(15, 23, 42, 0.05);
--glow-primary: 0 0 0 3px rgba(80, 167, 250, 0.2);
}
// =============================================================================
// CSS Custom Properties — Dark Mode Overrides
// =============================================================================
@media (prefers-color-scheme: dark) {
:root {
// Surface
--color-surface: #{$dk-surface};
--color-surface-dim: #{$dk-surface-dim};
--color-surface-bright: #{$dk-surface-bright};
--color-surface-container-lowest: #{$dk-surface-container-lowest};
--color-surface-container-low: #{$dk-surface-container-low};
--color-surface-container: #{$dk-surface-container};
--color-surface-container-high: #{$dk-surface-container-high};
--color-surface-container-highest: #{$dk-surface-container-highest};
--color-on-surface: #{$dk-on-surface};
--color-on-surface-variant: #{$dk-on-surface-variant};
--color-outline: #{$dk-outline};
--color-outline-variant: #{$dk-outline-variant};
--color-surface-tint: #{$dk-surface-tint};
--color-surface-variant: #{$dk-surface-variant};
--color-surface-container-lowest-rgb: 6, 14, 32;
// Primary
--color-primary: #{$dk-primary};
--color-on-primary: #{$dk-on-primary};
--color-primary-container: #{$dk-primary-container};
--color-on-primary-container: #{$dk-on-primary-container};
--color-inverse-primary: #{$dk-inverse-primary};
// Secondary
--color-secondary: #{$dk-secondary};
--color-on-secondary: #{$dk-on-secondary};
--color-secondary-container: #{$dk-secondary-container};
--color-on-secondary-container: #{$dk-on-secondary-container};
// Tertiary
--color-tertiary: #{$dk-tertiary};
--color-on-tertiary: #{$dk-on-tertiary};
--color-tertiary-container: #{$dk-tertiary-container};
--color-on-tertiary-container: #{$dk-on-tertiary-container};
// Error
--color-error: #{$dk-error};
--color-on-error: #{$dk-on-error};
--color-error-container: #{$dk-error-container};
--color-on-error-container: #{$dk-on-error-container};
// Background
--color-background: #{$dk-background};
--color-on-background: #{$dk-on-background};
// Elevation adjustments for dark backgrounds
--shadow-card-hover: 0px 4px 20px rgba(0, 0, 0, 0.4);
}
}
// =============================================================================
// Base Styles
// =============================================================================
body {
background-color: var(--color-background);
color: var(--color-on-surface);
font-family: var(--font-body);
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-display);
}
code, kbd, pre, samp {
font-family: var(--font-mono);
}
// =============================================================================
// Typography Utility Classes
// =============================================================================
.text-display-lg {
font-family: var(--font-display);
font-size: 64px;
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.02em;
@media (max-width: 768px) {
font-size: 40px;
line-height: 1.2;
}
}
.text-headline-md {
font-family: var(--font-display);
font-size: 32px;
font-weight: 600;
line-height: 1.3;
}
.text-body-lg {
font-family: var(--font-body);
font-size: 18px;
line-height: 1.6;
}
.text-body-md {
font-family: var(--font-body);
font-size: 16px;
line-height: 1.5;
}
// Primary label style (light design doc)
.text-label-sm {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 500;
line-height: 1;
letter-spacing: 0.05em;
}
// Variant with uppercase transform (dark design doc)
.text-label-caps {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.05em;
text-transform: uppercase;
}
// =============================================================================
// Component Overrides
// All colors use var(--color-*) to auto-switch between modes.
// =============================================================================
// --- Buttons ---
.btn-primary {
// #50a7fa is the shared anchor; light text gives better contrast
color: $lt-on-primary; // #ffffff
&:hover,
&:focus-visible {
color: $lt-on-primary;
box-shadow: var(--glow-primary);
}
}
.btn-outline-primary {
&:hover,
&:focus-visible {
box-shadow: var(--glow-primary);
}
}
// --- Cards ---
// Project cards: border shifts to azure on hover, subtle shadow appears
.card {
background-color: var(--color-surface-container);
border: 1px solid var(--color-outline-variant);
border-radius: var(--radius-lg); // 1rem — large containers per design doc
box-shadow: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&:hover {
border-color: #50a7fa;
box-shadow: var(--shadow-card-hover);
}
.card-title {
font-family: var(--font-display);
color: var(--color-on-surface);
}
}
// --- Form Inputs ---
.form-control,
.form-select {
background-color: var(--color-surface-container-low);
border-color: var(--color-outline-variant);
color: var(--color-on-surface);
&::placeholder {
color: var(--color-on-surface-variant);
opacity: 1;
}
&:focus {
background-color: var(--color-surface-container-low);
border-color: #50a7fa;
color: var(--color-on-surface);
box-shadow: var(--glow-primary);
}
}
// JetBrains Mono for all form labels
label,
.form-label {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 500;
letter-spacing: 0.05em;
color: var(--color-on-surface-variant);
}
// --- Navbar ---
// Sticky glassmorphism; links use JetBrains Mono (label-sm style)
.navbar {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
// rgba() with RGB channel variable — correct way to compose transparency on a CSS var color
background-color: rgba(var(--color-surface-container-lowest-rgb), 0.85);
border-bottom: 1px solid var(--color-outline-variant);
.nav-link {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 500;
letter-spacing: 0.05em;
color: var(--color-on-surface);
transition: color 0.15s ease;
&:hover,
&.active {
color: #50a7fa;
}
}
}
// --- Tech Chips / Badges ---
.badge-tech {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 500;
letter-spacing: 0.05em;
border-radius: var(--radius);
padding: 4px 8px;
background-color: rgba(80, 167, 250, 0.1);
// --color-primary is #0061a2 (light) / #9dcaff (dark) — both readable on their backgrounds
color: var(--color-primary);
border: 1px solid rgba(80, 167, 250, 0.25);
}
// --- Code Blocks ---
pre {
position: relative;
background-color: var(--color-surface-container-lowest);
border: 1px solid var(--color-outline-variant);
border-radius: var(--radius);
code {
font-family: var(--font-mono);
font-size: 14px;
color: var(--color-on-surface);
}
.code-lang {
position: absolute;
top: var(--spacing-sm);
right: var(--spacing-md);
font-family: var(--font-mono);
font-size: 12px;
font-weight: 500;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--color-on-surface-variant);
}
}
// --- Interactive Glow Helper ---
.interactive-glow {
transition: box-shadow 0.2s ease;
&:hover,
&:focus {
box-shadow: var(--glow-primary);
}
}
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 20px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
}
+20
View File
@@ -0,0 +1,20 @@
.footer-link {
color: rgba(255, 255, 255, .6);
transition: color .15s ease;
}
.footer-link:hover {
color: var(--color-primary-container);
}
#footer {
z-index: 50;
background: rgba(0,0,0,.3);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-color: rgba(192, 199, 211, .1) !important;
}
#footer-copyright {
color: rgba(255,255,255,.4);
}
+31
View File
@@ -0,0 +1,31 @@
nav div {
height: 64px;
}
/* Glassmorphism nav — uses RGB channel var for rgba() composition */
.nav-glass {
background-color: rgba(var(--color-surface-container-lowest-rgb), .85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
/* Nav link colours — needs :hover, can't do inline */
.nav-link-ds {
color: var(--color-on-surface-variant);
transition: color .15s ease;
}
.nav-link-ds:hover {
color: #50a7fa;
}
#top-nav {
z-index: 50;
border-color: rgba(192, 199, 211, .3) !important;
}
#nav-site-name {
font-family: var(--font-display);
letter-spacing: -.02em;
font-size: 1rem;
}
+154
View File
@@ -0,0 +1,154 @@
body {
height: 100vh;
}
/* Scanline sweep */
.scanline {
position: absolute;
bottom: 100%;
width: 100%;
height: 100px;
pointer-events: none;
background: linear-gradient(0deg, transparent 0%, rgba(80, 167, 250, .05) 50%, transparent 100%);
opacity: .1;
animation: scanline 8s linear infinite;
}
@keyframes scanline {
0% {
bottom: 100%;
}
100% {
bottom: -100px;
}
}
/* Primary hero button shimmer */
.hero-btn-primary {
position: relative;
overflow: hidden;
}
.hero-btn-primary::before {
content: '';
position: absolute;
inset: 0;
background: rgba(255, 255, 255, .2);
transform: translateY(100%);
transition: transform .3s ease;
}
.hero-btn-primary:hover::before {
transform: translateY(0);
}
/* Azure glow on hover */
.glow-hover:hover {
box-shadow: 0 0 25px rgba(80, 167, 250, .4);
}
.badge {
background: rgba(80, 167, 250, .1);
border: 1px solid rgba(80, 167, 250, .3);
}
.badge .text-label-sm {
color: var(--color-primary-container);
letter-spacing: .15em;
}
/* Pulsing availability dot */
.dot-pulse {
animation: dot-pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
display: inline-block;
width: 8px;
height: 8px;
background: #50a7fa;
}
@keyframes dot-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .35;
}
}
.headline {
text-shadow: 0 2px 4px rgba(0, 0, 0, .8);
}
.headline span {
color: var(--color-primary-container);
}
.subheadline {
max-width: 640px;
color: rgba(255, 255, 255, .85);
text-shadow: 0 1px 2px rgba(0, 0, 0, .9);
}
/* Scroll indicator line */
.scroll-line {
width: 1px;
height: 48px;
background: linear-gradient(to bottom, #50a7fa, transparent);
}
.hud-decorator-line {
width: 128px;
height: 1px;
background-color: var(--color-primary-container);
}
.hud-decorator {
opacity: .4;
}
.hud-decorator p {
color: var(--color-primary-container);
letter-spacing: .3em;
}
#bg {
z-index: 0;
}
#bg-img {
filter: grayscale(.2) brightness(.75);
transform: scale(1.05);
}
#bg-overlay {
background: linear-gradient(to bottom, rgba(0, 0, 0, .6) 0%, transparent 50%, rgba(0, 0, 0, .8) 100%);
}
#content-div {
z-index: 10;
min-height: 100vh;
}
#hud-decorator-top-left {
margin-top: 96px;
}
#hud-decorator-bottom-right {
margin-bottom: 96px;
}
#hud-decorator-bottom span {
letter-spacing: .2em;
}
#hero-content {
max-width: 896px;
}
#button-about-me {
background: rgba(255, 255, 255, .05);
border: 1px solid rgba(255, 255, 255, .2);
backdrop-filter: blur(4px);
}
+11
View File
@@ -0,0 +1,11 @@
import "../../styles/root/root.css";
document.addEventListener('mousemove', (e) => {
const bg = document.getElementById('bg-img');
const x = (e.clientX - window.innerWidth / 2) * 0.005;
const y = (e.clientY - window.innerHeight / 2) * 0.005;
if (bg) {
bg.style.transform = `scale(1.05) translate(${x}px, ${y}px)`;
}
});
+9 -2
View File
@@ -10,14 +10,20 @@
"ext-ctype": "*",
"ext-iconv": "*",
"ext-redis": "*",
"symfony/asset": "8.1.*",
"symfony/asset-mapper": "8.1.*",
"symfony/cache": "8.1.*",
"symfony/console": "8.1.*",
"symfony/dotenv": "8.1.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "8.1.*",
"symfony/runtime": "8.1.*",
"symfony/twig-bundle": "8.1.*",
"symfony/web-link": "8.1.*",
"symfony/webpack-encore-bundle": "^2.4",
"symfony/yaml": "8.1.*"
"symfony/yaml": "8.1.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
},
"require-dev": {
"symfony/debug-bundle": "8.1.*",
@@ -47,7 +53,8 @@
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
"assets:install %PUBLIC_DIR%": "symfony-cmd",
"importmap:install": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
Generated
+906 -355
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -7,4 +7,5 @@ return [
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
];
+11
View File
@@ -0,0 +1,11 @@
framework:
asset_mapper:
# The paths to make available to the asset mapper.
paths:
- assets/
missing_import_mode: strict
when@prod:
framework:
asset_mapper:
missing_import_mode: warn
+4
View File
@@ -17,3 +17,7 @@ when@test:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
when@prod:
framework:
http_cache: true
+54 -3
View File
@@ -286,7 +286,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* }>,
* },
* asset_mapper?: bool|array{ // Asset Mapper configuration
* enabled?: bool|Param, // Default: false
* enabled?: bool|Param, // Default: true
* paths?: string|array<string, scalar|Param|null>,
* excluded_patterns?: list<scalar|Param|null>,
* exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true
@@ -416,7 +416,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* log_channel?: scalar|Param|null, // The channel of log message. Null to let Symfony decide. // Default: null
* }>,
* web_link?: bool|array{ // Web links configuration
* enabled?: bool|Param, // Default: false
* enabled?: bool|Param, // Default: true
* },
* lock?: bool|string|array{ // Lock configuration
* enabled?: bool|Param, // Default: false
@@ -471,7 +471,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* },
* disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true
* http_client?: bool|array{ // HTTP Client configuration
* enabled?: bool|Param, // Default: false
* enabled?: bool|Param, // Default: true
* max_host_connections?: int|Param, // The maximum number of connections to a single host.
* default_options?: array{
* headers?: array<string, mixed>,
@@ -763,6 +763,53 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* script_attributes?: array<string, scalar|Param|null>,
* link_attributes?: array<string, scalar|Param|null>,
* }
* @psalm-type TwigExtraConfig = array{
* cache?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* html?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* markdown?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* intl?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* cssinliner?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* inky?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* string?: bool|array{
* enabled?: bool|Param, // Default: false
* },
* commonmark?: array{
* renderer?: array{ // Array of options for rendering HTML.
* block_separator?: scalar|Param|null,
* inner_separator?: scalar|Param|null,
* soft_break?: scalar|Param|null,
* },
* html_input?: "strip"|"allow"|"escape"|Param, // How to handle HTML input.
* allow_unsafe_links?: bool|Param, // Remove risky link and image URLs by setting this to false. // Default: true
* max_nesting_level?: int|Param, // The maximum nesting level for blocks. // Default: 9223372036854775807
* max_delimiters_per_line?: int|Param, // The maximum number of strong/emphasis delimiters per line. // Default: 9223372036854775807
* slug_normalizer?: array{ // Array of options for configuring how URL-safe slugs are created.
* instance?: mixed,
* max_length?: int|Param, // Default: 255
* unique?: mixed,
* },
* commonmark?: array{ // Array of options for configuring the CommonMark core extension.
* enable_em?: bool|Param, // Default: true
* enable_strong?: bool|Param, // Default: true
* use_asterisk?: bool|Param, // Default: true
* use_underscore?: bool|Param, // Default: true
* unordered_list_markers?: list<scalar|Param|null>,
* },
* ...<string, mixed>
* },
* }
* @psalm-type ConfigType = array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@@ -770,6 +817,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* framework?: FrameworkConfig,
* twig?: TwigConfig,
* webpack_encore?: WebpackEncoreConfig,
* twig_extra?: TwigExtraConfig,
* "when@dev"?: array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@@ -780,6 +828,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* twig?: TwigConfig,
* maker?: MakerConfig,
* webpack_encore?: WebpackEncoreConfig,
* twig_extra?: TwigExtraConfig,
* },
* "when@prod"?: array{
* imports?: ImportsConfig,
@@ -788,6 +837,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* framework?: FrameworkConfig,
* twig?: TwigConfig,
* webpack_encore?: WebpackEncoreConfig,
* twig_extra?: TwigExtraConfig,
* },
* "when@test"?: array{
* imports?: ImportsConfig,
@@ -798,6 +848,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* debug?: DebugConfig,
* twig?: TwigConfig,
* webpack_encore?: WebpackEncoreConfig,
* twig_extra?: TwigExtraConfig,
* },
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig,
+19
View File
@@ -0,0 +1,19 @@
<?php
/**
* Returns the importmap for this application.
*
* - "path" is a path inside the asset mapper system. Use the
* "debug:asset-map" command to see the full list of paths.
*
* - "entrypoint" (JavaScript only) set to true for any module that will
* be used as an "entrypoint" (and passed to the importmap() Twig function).
*
* The "importmap:require" command can be used to add new entries to this file.
*/
return [
'app' => [
'path' => './assets/app.js',
'entrypoint' => true,
],
];
+4
View File
@@ -20,5 +20,9 @@
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8"
}
}
+1
View File
@@ -24,6 +24,7 @@ server {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param PHP_IDE_CONFIG "serverName=localhost";
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class RootController extends AbstractController
{
#[Route('/', name: 'app_root')]
public function index(): Response
{
return $this->render('root/root.html.twig', [
'controller_name' => 'RootController',
]);
}
}
+19 -1
View File
@@ -1,4 +1,19 @@
{
"symfony/asset-mapper": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "c01c47af2ec66a74ec046eccb919cfe27d3464ec"
},
"files": [
"assets/app.js",
"assets/styles/app.css",
"config/packages/asset_mapper.yaml",
"importmap.php"
]
},
"symfony/console": {
"version": "8.1",
"recipe": {
@@ -113,11 +128,14 @@
"ref": "b346dae458e64a1921ded2125993d94bd719a8dd"
},
"files": [
"assets/app.ts",
"assets/app.js",
"assets/styles/app.css",
"config/packages/webpack_encore.yaml",
"package.json",
"webpack.config.js"
]
},
"twig/extra-bundle": {
"version": "v3.24.0"
}
}
+20 -12
View File
@@ -1,29 +1,37 @@
<!DOCTYPE html>
<html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}creative-dragonslayer.de{% endblock %}</title>
<link rel="icon" href="{{ asset('images/logo.png') }}">
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block head %}{% endblock %}
{% set frankenphpHotReload = app.request.server.get('FRANKENPHP_HOT_RELOAD') %}
{% if frankenphpHotReload %}
<meta name="frankenphp-hot-reload:url" content="{{ frankenphpHotReload }}">
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
<meta name="frankenphp-hot-reload:url" content="{{ frankenphpHotReload }}">
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
{% endif %}
</head>
<body>
{% include 'widgets/nav_bar.html.twig' %}
<main>
{% block body %}{% endblock %}
{% block body %}{% endblock %}
</main>
<footer>
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</footer>
{% include 'widgets/footer.html.twig' %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>
+97
View File
@@ -0,0 +1,97 @@
{% extends 'base.html.twig' %}
{% block title %}creative-dragonslayer.de | Deep Space Engineering{% endblock %}
{% block stylesheets %}
{{ parent() }}
{{ encore_entry_link_tags('root') }}
<link rel="preload" href="{{ preload(asset('images/root/background.jpg'), { as: 'image' }) }}">
{% endblock %}
{% block javascripts %}
{{ parent() }}
{{ encore_entry_script_tags('root') }}
{% endblock %}
{% block body %}
{# ── Fixed Background ────────────────────────────────────────────────────── #}
<div id="bg" class="position-fixed top-0 start-0 w-100 h-100">
<img
id="bg-img"
src="{{ asset('images/root/background.jpg') }}"
alt="Hintergrundbild"
class="w-100 h-100 object-fit-cover"
>
<div id="bg-overlay" class="position-absolute top-0 start-0 w-100 h-100"></div>
<div class="scanline"></div>
</div>
<div id="content-div" class="position-relative d-flex flex-column align-items-center justify-content-center
w-100 text-center px-4">
{# HUD Decorator — Top Left #}
<div id="hud-decorator-top-left"
class="hud-decorator position-absolute top-0 start-0 d-none d-xl-flex flex-column gap-3 ps-4">
<div class="hud-decorator-line"></div>
<p class="text-label-sm mb-0 text-uppercase">System.Status: Stable</p>
<p class="text-label-sm mb-0 text-uppercase">Lat: 52.5200° N</p>
</div>
{# HUD Decorator — Bottom Right #}
<div id="hud-decorator-bottom-right"
class="hud-decorator position-absolute bottom-0 end-0 d-none d-xl-flex flex-column align-items-end gap-3 pe-4">
<p class="text-label-sm mb-0 text-end text-uppercase">V.0.9.0 // CORE</p>
<p class="text-label-sm mb-0 text-end text-uppercase">Syncing Precision...</p>
<div class="hud-decorator-line"></div>
</div>
{# Hero Content #}
<div id="hero-content" class="d-flex flex-column align-items-center gap-4">
{# Badge #}
<div class="badge d-inline-flex align-items-center gap-2 px-3 py-1 rounded-pill">
<span class="rounded-circle dot-pulse"></span>
<span class="text-label-sm text-uppercase">
Debugging Life Since 2018
</span>
</div>
{# Headline #}
<h1 class="text-display-lg mb-0 text-white lh-1 headline">
CREATIVE
<span class="fst-italic fw-light">
DRAGONSLAYER
</span>
</h1>
{# Subheadline #}
<p class="text-body-lg mb-0 mx-auto subheadline">
Code, Kommandozeile und Kaffee. Dies ist eine Sammlung meiner Hobbyprojekte, entwickelt mit
Leidenschaft. Hier geht es nicht um Skalierung oder Marktanteile, sondern darum, Probleme zu lösen,
die mich beschäftigen, und Techniken auszuprobieren, die mich neugierig machen.
</p>
{# CTAs #}
<div class="d-flex flex-column flex-sm-row gap-3 justify-content-center align-items-center mt-2">
<a href="#projects"
class="hero-btn-primary btn btn-primary text-white fw-bold px-5 py-3 rounded-3
glow-hover d-inline-flex align-items-center gap-2">
Zu den Projekten
<span class="material-symbols-outlined">arrow_forward</span>
</a>
<a href="#about" id="button-about-me" class="btn fw-bold px-5 py-3 rounded-3 text-white">
Werdegang
</a>
</div>
</div>
{# Scroll Indicator #}
<div id="hud-decorator-bottom" class="position-absolute bottom-0 start-50 translate-middle-x
d-flex flex-column align-items-center gap-2 pb-4 hud-decorator">
<span class="text-label-sm text-white text-uppercase">Deploying Scroll</span>
<div class="scroll-line"></div>
</div>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
<footer class="position-fixed bottom-0 start-0 end-0 border-top"
id="footer">
<div class="container d-flex flex-column flex-md-row justify-content-between align-items-center py-3">
<div id="footer-copyright" class="text-label-sm fw-bold">
© 2026 creative-dragonslayer.de
</div>
<div class="d-flex gap-4 mt-2 mt-md-0">
{# <a href="#" class="text-label-sm text-decoration-none footer-link">Impressum</a>#}
{# <a href="#" class="text-label-sm text-decoration-none footer-link">Datenschutz</a>#}
{# <a href="#" class="text-label-sm text-decoration-none footer-link">Nutzungsbedingungen</a>#}
<a href="#" class="text-label-sm text-decoration-none footer-link">Credits</a>
</div>
</div>
</footer>
+17
View File
@@ -0,0 +1,17 @@
<nav class="position-fixed top-0 start-0 end-0 nav-glass border-bottom"
id="top-nav"
aria-label="Hauptnavigation">
<div class="container d-flex justify-content-between align-items-center">
<span id="nav-site-name" class="fw-bold text-white">
CTDRA
</span>
<div class="d-none d-md-flex align-items-center gap-4">
<a href="#" class="text-label-sm text-decoration-none nav-link-ds">News</a>
<a href="#" class="text-label-sm text-decoration-none nav-link-ds">Projects</a>
<a href="#" class="text-label-sm text-decoration-none nav-link-ds">About Me</a>
</div>
<div>&nbsp;</div>
</div>
</nav>
+6 -1
View File
@@ -8,7 +8,12 @@
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true
"noEmit": true,
"plugins":[
{
"name": "typescript-plugin-css"
}
]
},
"include": ["assets/**/*"]
}
+6 -1
View File
@@ -20,7 +20,9 @@ Encore
* Each entry will result in one JavaScript file (e.g. app.js)
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
*/
.addEntry('app', './assets/app.ts')
.addEntry('app', './assets/app.js')
.addEntry('root', './assets/ts/root/root.ts')
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
.splitEntryChunks()
@@ -61,6 +63,9 @@ Encore
// uncomment if you use TypeScript
.enableTypeScriptLoader()
// optionally enable forked type script for faster builds
// https://www.npmjs.com/package/fork-ts-checker-webpack-plugin
// requires that you have a tsconfig.json file that is setup correctly.
.enableForkedTypeScriptTypesChecking()
// uncomment if you use React
+10
View File
@@ -972,6 +972,11 @@
"@parcel/watcher-win32-ia32" "2.5.6"
"@parcel/watcher-win32-x64" "2.5.6"
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@sinclair/typebox@^0.34.0":
version "0.34.49"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.49.tgz#4f1369234f2ecf693866476c3b2e1b54d2a9d68e"
@@ -1323,6 +1328,11 @@ boolbase@^1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
bootstrap@^5.3.8:
version "5.3.8"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed"
integrity sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==
brace-expansion@^1.1.7:
version "1.1.15"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.15.tgz#a6d90d54067236e5f42570a3b7378d594d9b7738"