feat: Commit initial avec application Quarkus JSF et page d'accueil

- Ajout de la configuration JSF/Quarkus
- Ajout des contrôleurs et composants JSF
- Ajout de la page d'accueil avec animations
- Configuration Docker et environnement de build
This commit is contained in:
DahoudG
2024-12-08 11:08:24 +00:00
commit a276ac2318
21 changed files with 1490 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/lionsdev-client-impl-quarkus-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/lionsdev-client-impl-quarkus-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/lionsdev-client-impl-quarkus-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.20
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@@ -0,0 +1,93 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/lionsdev-client-impl-quarkus-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/lionsdev-client-impl-quarkus-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/lionsdev-client-impl-quarkus-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.20
ENV LANGUAGE='en_US:en'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@@ -0,0 +1,27 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/lionsdev-client-impl-quarkus .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/lionsdev-client-impl-quarkus
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.10
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@@ -0,0 +1,30 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/lionsdev-client-impl-quarkus .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/lionsdev-client-impl-quarkus
#
###
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@@ -0,0 +1,9 @@
package dev.lions.config;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.annotation.FacesConfig;
@ApplicationScoped
@FacesConfig
public class JSFConfiguration {
}

View File

@@ -0,0 +1,17 @@
package dev.lions.controllers;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import java.io.Serializable;
@Named
@RequestScoped
public class HomeController implements Serializable {
private static final long serialVersionUID = 1L;
public String getWelcomeMessage() {
System.out.println("HomeController.getWelcomeMessage() called");
return "Welcome to Lions Dev";
}
}

View File

@@ -0,0 +1,19 @@
package dev.lions.controllers;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
@Named
@RequestScoped
public class IndexController {
private String message = "Welcome to Lions Dev!";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="4.0"
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd">
<name>LionsDev</name>
<application>
<locale-config>
<default-locale>en</default-locale>
<supported-locale>fr</supported-locale>
</locale-config>
</application>
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<description>Page index</description>
<from-outcome>index</from-outcome>
<to-view-id>/private/index.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
</faces-config>

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:ui="jakarta.faces.facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lions Dev - <ui:insert name="title">Welcome</ui:insert></title>
<h:outputStylesheet name="css/custom.css" />
<h:outputStylesheet name="css/animations.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
</h:head>
<h:body>
<div class="layout-wrapper">
<!-- Header -->
<header class="layout-header">
<div class="header-content">
<div class="logo">
<h:graphicImage name="images/logo.png" alt="Lions Dev Logo" height="50"/>
</div>
<nav>
<ul class="nav-list">
<li><a href="#home" class="nav-link">Accueil</a></li>
<li><a href="#services" class="nav-link">Services</a></li>
<li><a href="#contact" class="nav-link nav-button">Contact</a></li>
</ul>
</nav>
</div>
<div class="mobile-menu-button">
<i class="fas fa-bars"></i>
</div>
</header>
<!-- Main Content -->
<main>
<ui:insert name="content">
<p>Default content</p>
</ui:insert>
</main>
<!-- Footer -->
<footer class="layout-footer">
<div class="footer-content">
<div class="footer-section">
<h3>À propos</h3>
<p>Lions Dev - Intégrateur de solutions digitales</p>
</div>
<div class="footer-section">
<h3>Contact</h3>
<p>Email: contact@lions.dev</p>
</div>
<div class="footer-section">
<h3>Suivez-nous</h3>
<div class="social-links">
<a href="#"><i class="fab fa-linkedin"></i></a>
<a href="#"><i class="fab fa-github"></i></a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>© 2024 Lions Dev. Tous droits réservés.</p>
</div>
</footer>
</div>
<h:outputScript name="js/custom.js" />
<h:outputScript name="js/animations.js" />
</h:body>
</html>

View File

@@ -0,0 +1,162 @@
.animate-on-scroll {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease-out;
}
.animate-on-scroll.animated {
opacity: 1;
transform: translateY(0);
}
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease-out;
}
.slide-in-left {
opacity: 0;
transform: translateX(-50px);
transition: all 0.6s ease-out;
}
.slide-in-right {
opacity: 0;
transform: translateX(50px);
transition: all 0.6s ease-out;
}
.animated {
opacity: 1;
transform: translate(0, 0);
}
.hover-effect {
transition: transform 0.3s ease-in-out;
}
.hover-effect:hover {
transform: translateY(-10px);
}
.icon-animate {
transition: all 0.3s ease-in-out;
}
.card-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(33, 150, 243, 0.9);
opacity: 0;
display: flex;
justify-content: center;
align-items: center;
transition: opacity 0.3s ease-in-out;
border-radius: 8px;
}
.overlay-text {
color: white;
font-size: 1.2rem;
font-weight: bold;
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
/* Stats Container Styles */
.stats-container {
display: flex;
justify-content: space-around;
margin: 2rem 0;
flex-wrap: wrap;
}
.stat-item {
text-align: center;
padding: 1rem;
}
.counter {
font-size: 2.5rem;
font-weight: bold;
color: var(--primary-color);
}
/* Loading Button Styles */
.loading-button {
position: relative;
transition: all 0.3s ease;
}
.loading-button.loading {
background-color: #ccc;
cursor: not-allowed;
}
.loading-button.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
right: 10px;
transform: translateY(-50%);
border: 2px solid #fff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: translateY(-50%) rotate(360deg);
}
}
/* Dans animations.css */
.service-card {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.service-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
opacity: 0;
transition: opacity 0.3s ease;
}
.service-card:hover::before {
opacity: 0.1;
}
.service-card .icon-animate {
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 1rem;
display: inline-block;
}

View File

@@ -0,0 +1,219 @@
/* Global Styles */
:root {
--primary-color: #2196F3;
--secondary-color: #1976D2;
--text-color: #333333;
--background-color: #FFFFFF;
}
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: var(--text-color);
background-color: var(--background-color);
}
/* Layout */
.layout-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header */
.layout-header {
background-color: var(--background-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 1rem 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.nav-list {
display: flex;
list-style: none;
gap: 2rem;
margin: 0;
padding: 0;
}
.nav-button {
background-color: var(--primary-color);
color: white;
padding: 0.5rem 1rem;
border-radius: 4px;
text-decoration: none;
}
/* Hero Section */
.hero-section {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)),
url('#{resource["images/pattern.png"]}') repeat;
color: white;
padding: 4rem 2rem;
text-align: center;
position: relative;
overflow: hidden;
}
.hero-content {
max-width: 800px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.hero-button {
font-size: 1.2rem;
padding: 1rem 2rem;
margin-top: 2rem;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(33, 150, 243, 0.9), rgba(25, 118, 210, 0.9));
}
/* Services Section */
.services-section {
padding: 4rem 2rem;
background-color: #f5f5f5;
}
.service-card {
background-color: white;
padding: 2rem;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Footer */
.layout-footer {
background-color: #333;
color: white;
padding: 3rem 2rem 1rem;
margin-top: auto;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.footer-bottom {
text-align: center;
padding-top: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 2rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.nav-list {
display: none;
}
.header-content {
flex-direction: column;
gap: 1rem;
}
}
.layout-header {
background-color: var(--background-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 1rem 2rem;
position: fixed;
width: 100%;
top: 0;
z-index: 1000;
transition: all 0.3s ease;
}
.layout-header.scroll-down {
transform: translateY(-100%);
}
.layout-header.scroll-up {
transform: translateY(0);
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* Ajout d'un padding-top au main pour compenser le header fixe */
main {
padding-top: 80px;
}
/* Amélioration du style des boutons */
.hero-button {
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
border: none;
color: white;
padding: 1rem 2.5rem;
border-radius: 50px;
font-size: 1.1rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3);
}
.hero-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
}
/* Style pour la grille de services */
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.mobile-menu-button {
display: none;
font-size: 1.5rem;
cursor: pointer;
}
@media (max-width: 768px) {
.mobile-menu-button {
display: block;
}
.nav-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
flex-direction: column;
padding: 1rem;
display: none;
}
.nav-list.active {
display: flex;
}
.nav-list li {
margin: 0.5rem 0;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1,119 @@
document.addEventListener('DOMContentLoaded', function() {
// Initialize animations
initializeAnimations();
// Start counters when visible
initializeCounters();
// Initialize parallax effect
initializeParallax();
});
function initializeAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animated');
if (entry.target.classList.contains('fade-in')) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
}
});
}, { threshold: 0.1 });
// Observe all elements with animation classes
document.querySelectorAll('.animate-on-scroll, .fade-in, .slide-in-left, .slide-in-right')
.forEach(element => observer.observe(element));
}
function handleCardHover(card, isHovering) {
const icon = card.querySelector('.icon-animate');
const overlay = card.querySelector('.card-overlay');
if (isHovering) {
icon.style.transform = 'scale(1.2) translateY(-10px)';
overlay.style.opacity = '1';
} else {
icon.style.transform = 'scale(1) translateY(0)';
overlay.style.opacity = '0';
}
}
function initializeCounters() {
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const counter = entry.target;
const target = parseInt(counter.dataset.count);
animateCounter(counter, target);
counterObserver.unobserve(counter);
}
});
}, { threshold: 0.5 });
document.querySelectorAll('.stat-item').forEach(stat => {
counterObserver.observe(stat);
});
}
function animateCounter(element, target) {
const counter = element.querySelector('.counter');
let current = 0;
const increment = target / 50; // Divide animation into 50 steps
const duration = 2000; // 2 seconds
const interval = duration / 50;
const timer = setInterval(() => {
current += increment;
counter.textContent = Math.round(current);
if (current >= target) {
counter.textContent = target;
clearInterval(timer);
}
}, interval);
}
function initializeParallax() {
window.addEventListener('scroll', () => {
const parallaxElements = document.querySelectorAll('[data-parallax="scroll"]');
parallaxElements.forEach(element => {
const speed = element.dataset.speed || 0.5;
const offset = window.pageYOffset;
element.style.backgroundPositionY = `${offset * speed}px`;
});
});
}
function handleButtonClick(button) {
// Add loading state
button.classList.add('loading');
button.disabled = true;
// Store original text
const originalText = button.textContent;
button.textContent = 'Loading...';
// Simulate loading (remove in production)
setTimeout(() => {
button.classList.remove('loading');
button.disabled = false;
button.textContent = originalText;
}, 1000);
return true; // Allow navigation to proceed
}
// Add smooth scrolling for all anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});

View File

@@ -0,0 +1,176 @@
document.addEventListener('DOMContentLoaded', function() {
// Navigation mobile toggle
const mobileMenuButton = document.querySelector('.mobile-menu-button');
const navList = document.querySelector('.nav-list');
if (mobileMenuButton) {
mobileMenuButton.addEventListener('click', function() {
navList.classList.toggle('active');
});
}
// Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Header scroll effect
const header = document.querySelector('.layout-header');
let lastScroll = 0;
window.addEventListener('scroll', () => {
const currentScroll = window.pageYOffset;
if (currentScroll <= 0) {
header.classList.remove('scroll-up');
return;
}
if (currentScroll > lastScroll && !header.classList.contains('scroll-down')) {
// Down
header.classList.remove('scroll-up');
header.classList.add('scroll-down');
} else if (currentScroll < lastScroll && header.classList.contains('scroll-down')) {
// Up
header.classList.remove('scroll-down');
header.classList.add('scroll-up');
}
lastScroll = currentScroll;
});
// Intersection Observer for animations
const animateOnScroll = () => {
const elements = document.querySelectorAll('.animate-on-scroll');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animated');
}
});
}, {
threshold: 0.1
});
elements.forEach(element => {
observer.observe(element);
});
};
animateOnScroll();
// Form validation enhancement
const enhanceFormValidation = () => {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
const inputs = form.querySelectorAll('input, textarea');
inputs.forEach(input => {
// Real-time validation
input.addEventListener('input', function() {
validateInput(this);
});
// Blur validation
input.addEventListener('blur', function() {
validateInput(this);
});
});
});
};
const validateInput = (input) => {
const parent = input.parentElement;
const errorElement = parent.querySelector('.error-message') ||
document.createElement('span');
errorElement.classList.add('error-message');
if (!input.value && input.hasAttribute('required')) {
errorElement.textContent = 'Ce champ est requis';
!parent.contains(errorElement) && parent.appendChild(errorElement);
} else if (input.type === 'email' && !validateEmail(input.value)) {
errorElement.textContent = 'Email invalide';
!parent.contains(errorElement) && parent.appendChild(errorElement);
} else {
errorElement.remove();
}
};
const validateEmail = (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
enhanceFormValidation();
// Loading state management for buttons
const setupLoadingButtons = () => {
const buttons = document.querySelectorAll('.loading-button');
buttons.forEach(button => {
button.addEventListener('click', function(e) {
if (!this.classList.contains('loading')) {
this.classList.add('loading');
this.setAttribute('disabled', 'disabled');
// Store original text
const originalText = this.textContent;
this.setAttribute('data-original-text', originalText);
this.textContent = 'Chargement...';
// Simulate loading (remove in production and handle with actual form submission)
setTimeout(() => {
this.classList.remove('loading');
this.removeAttribute('disabled');
this.textContent = originalText;
}, 2000);
}
});
});
};
setupLoadingButtons();
// Lazy loading for images
const setupLazyLoading = () => {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
};
setupLazyLoading();
});
// Service Worker Registration for PWA support
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker registration successful');
})
.catch(error => {
console.log('ServiceWorker registration failed:', error);
});
});
}

View File

@@ -0,0 +1,93 @@
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets"
xmlns:p="http://primefaces.org/ui"
template="/WEB-INF/template/page.xhtml">
<ui:define name="title">Accueil</ui:define>
<ui:define name="content">
<!-- Hero Section -->
<section class="hero-section" id="home">
<div class="hero-content fade-in">
<h1>#{homeController.welcomeMessage}</h1>
<p>Solutions digitales innovantes pour vos projets</p>
<p:button value="Découvrir nos services" styleClass="hero-button pulse-animation" />
</div>
</section>
<!-- Services Section -->
<section class="services-section" id="services">
<div class="services-content">
<h2 class="section-title animate-on-scroll">Nos Services</h2>
<div class="services-grid">
<div class="service-card hover-effect animate-on-scroll">
<i class="fas fa-code icon-animate"></i>
<h3>Développement Web</h3>
<p>Applications web modernes et performantes</p>
<div class="card-overlay">
<span class="overlay-text">En savoir plus</span>
</div>
</div>
<div class="service-card hover-effect animate-on-scroll">
<i class="fas fa-mobile-alt icon-animate"></i>
<h3>Applications Mobiles</h3>
<p>Solutions mobiles cross-platform</p>
<div class="card-overlay">
<span class="overlay-text">En savoir plus</span>
</div>
</div>
<div class="service-card hover-effect animate-on-scroll">
<i class="fas fa-cloud icon-animate"></i>
<h3>Cloud Solutions</h3>
<p>Infrastructure cloud sécurisée</p>
<div class="card-overlay">
<span class="overlay-text">En savoir plus</span>
</div>
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="stats-section">
<div class="stats-container">
<div class="stat-item" data-count="100">
<div class="counter">0</div>
<p>Projets Réalisés</p>
</div>
<div class="stat-item" data-count="50">
<div class="counter">0</div>
<p>Clients Satisfaits</p>
</div>
<div class="stat-item" data-count="5">
<div class="counter">0</div>
<p>Années d'Expérience</p>
</div>
</div>
</section>
<!-- Contact Section -->
<section class="contact-section" id="contact">
<h:form id="contactForm">
<div class="contact-content slide-in-right">
<h2>Contactez-nous</h2>
<div class="form-group">
<p:inputText placeholder="Votre nom" required="true" />
</div>
<div class="form-group">
<p:inputText placeholder="Votre email" required="true" />
</div>
<div class="form-group">
<p:inputTextarea placeholder="Votre message" required="true" />
</div>
<p:commandButton value="Envoyer" styleClass="loading-button"
onclick="return handleButtonClick(this);" />
</div>
</h:form>
</section>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="6.0" xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd">
<welcome-file-list>
<welcome-file>private/index.xhtml</welcome-file>
</welcome-file-list>
<context-param>
<param-name>jakarta.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
</web-app>

View File

@@ -0,0 +1,22 @@
# Configuration serveur
quarkus.http.port=8707
quarkus.servlet.context-path=/lions-dev
# Configuration JSF
quarkus.faces.enable=true
quarkus.faces.servlet-mappings=*.xhtml
quarkus.faces.project-stage=Development
# Configuration chemins d'acc<63>s
quarkus.http.auth.permission.public.paths=/private/*,/index.xhtml
quarkus.http.auth.permission.public.policy=permit
# Configuration ressources statiques
quarkus.http.static-resources.enabled=true
# Configuration PrimeFaces
quarkus.primefaces.theme=saga
# Configuration MyFaces
quarkus.myfaces.mapping-suffix=.xhtml
jakarta.faces.PROJECT_STAGE=Development