This commit is contained in:
AcuGIS 2024-01-28 00:16:08 +02:00
parent 537f04004a
commit f6b0d10927
156 changed files with 90638 additions and 1 deletions

282
Nominatim-Server.sh Normal file
View File

@ -0,0 +1,282 @@
#!/bin/bash -e
#Version: 3.1.0
#For use on clean Ubuntu 22 only!!!
#Cited, Inc https://www.citedcorp.com
#Usage Example for State of Delaware:
#./Nominatim-Server.sh http://download.geofabrik.de/north-america/us/delaware-latest.osm.pbf
#NOTE: It is best to run this via the screen command as it takes some time to finish.
PBF_URL="${1}"; #get URL from first parameter, http://download.geofabrik.de/europe/germany-latest.osm.pbf
PROJECT_NAME='nominatim'
NM_USER='ntim'; #nominatim website
NM_PG_PASS=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32);
HNAME=$(hostname -f)
PG_VER='14'
PGIS_VER='3'
function install_postgresql(){
#3. Install PostgreSQL
apt-get install -y postgresql-${PG_VER} postgresql-client-${PG_VER} postgresql-contrib-${PG_VER} \
postgresql-server-dev-${PG_VER} postgresql-${PG_VER}-postgis-${PGIS_VER} postgis
if [ ! -f /usr/lib/postgresql/${PG_VER}/bin/postgres ]; then
echo "Error: Get PostgreSQL version"; exit 1;
fi
ln -sf /usr/lib/postgresql/${PG_VER}/bin/pg_config /usr/bin
ln -sf /var/lib/postgresql/${PG_VER}/main/ /var/lib/postgresql
ln -sf /var/lib/postgresql/${PG_VER}/backups /var/lib/postgresql
systemctl start postgresql
#5. Set postgres Password
if [ $(grep -m 1 -c 'pg pass' /root/auth.txt) -eq 0 ]; then
PG_PASS=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32);
sudo -u postgres psql 2>/dev/null -c "alter user postgres with password '${PG_PASS}'"
echo "pg pass: ${PG_PASS}" > /root/auth.txt
fi
#4. Add Postgre variables to environment
if [ $(grep -m 1 -c 'PGDATA' /etc/environment) -eq 0 ]; then
cat >>/etc/environment <<CMD_EOF
export PGDATA=/var/lib/postgresql/${PG_VER}/main
CMD_EOF
fi
#6. Configure ph_hba.conf
cat >/etc/postgresql/${PG_VER}/main/pg_hba.conf <<CMD_EOF
local all all trust
host all all 127.0.0.1 255.255.255.255 md5
host all all 0.0.0.0/0 md5
host all all ::1/128 md5
hostssl all all 127.0.0.1 255.255.255.255 md5
hostssl all all 0.0.0.0/0 md5
hostssl all all ::1/128 md5
CMD_EOF
sed -i.save "s/.*listen_addresses.*/listen_addresses = '*'/" /etc/postgresql/${PG_VER}/main/postgresql.conf
#10. Create Symlinks for Backward Compatibility
mkdir -p /var/lib/pgsql
ln -sf /var/lib/postgresql/${PG_VER}/main /var/lib/pgsql
ln -sf /var/lib/postgresql/${PG_VER}/backups /var/lib/pgsql
systemctl restart postgresql
}
function install_prerequisites(){
apt-get install -y build-essential cmake g++ libboost-dev libboost-system-dev \
libboost-filesystem-dev libexpat1-dev zlib1g-dev libxml2-dev\
libbz2-dev libpq-dev liblua5.3-dev lua5.3 libgeos-dev libgeos++-dev libproj-dev \
postgresql-server-dev-${PG_VER} postgresql-${PG_VER}-postgis-${PGIS_VER} postgresql-contrib-${PG_VER} \
apache2 php php-{cgi,cli,intl,pgsql,pear,db} libapache2-mod-php \
libicu-dev python3-{dotenv,psycopg2,psutil,jinja2,icu,datrie,sqlalchemy,asyncpg} \
git python-pip python3-pyosmium osmosis libboost-python-dev nlohmann-json3-dev
}
function install_nominatim(){
#download
pushd /home/${NM_USER}
git clone --recursive https://github.com/openstreetmap/Nominatim.git
#compile
pushd Nominatim
git checkout v4.3.1
wget -O data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
mkdir build
pushd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make -j $(grep -c 'processor' /proc/cpuinfo)
make install
chown -R ${NM_USER}:${NM_USER} /home/${NM_USER}/Nominatim
popd
popd
popd
#Creating postgres accounts
su - postgres <<EOF
createuser -sd ${NM_USER}
createuser -SDR www-data
psql -c "alter user ${NM_USER} with password '${NM_PG_PASS}'"
EOF
}
function install_nominatim_ui(){
NMUI_VER='3.4.0'
pushd /home/${NM_USER}
wget -P/tmp https://github.com/osm-search/nominatim-ui/releases/download/v${NMUI_VER}/nominatim-ui-${NMUI_VER}.tar.gz
tar -xf /tmp/nominatim-ui-${NMUI_VER}.tar.gz
rm -rf /tmp/nominatim-ui-${NMUI_VER}.tar.gz
pushd nominatim-ui-${NMUI_VER}
mv dist/theme/config.theme.js.example dist/theme/config.theme.js
sed -i.save "s|Nominatim_Config.Nominatim_API_Endpoint =.*|Nominatim_Config.Nominatim_API_Endpoint = \"http://${HNAME}/nominatim/\"|" dist/theme/config.theme.js
popd
#Add webapp
mv nominatim-ui-${NMUI_VER}/dist/* /var/www/html/
rm -f /var/www/html/index.html
wget --quiet -P/tmp https://github.com/AcuGIS/Nominatim-Server/archive/refs/heads/master.zip
unzip /tmp/master.zip -d/tmp
cp -r /tmp/Nominatim-Server-master/app/* /var/www/html/
rm -rf /tmp/master.zip
#sed -i.save "s/localhost/${HNAME}/" /var/www/html/leaflet-example.html
chown -R www-data:www-data /var/www/html/
popd
}
function setup_nm_user(){
if [ $(grep -cm 1 "^${NM_USER}:" /etc/passwd) -eq 0 ]; then
useradd -m ${NM_USER}
fi
if [ $(grep -m 1 -c "${NM_USER} pass" /root/auth.txt) -eq 0 ]; then
USER_PASS=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32);
echo "${NM_USER}:${USER_PASS}" | chpasswd
echo "${NM_USER} pass: ${USER_PASS}" >> /root/auth.txt
fi
}
function download_optional_data(){
for f in wikipedia_article wikipedia_redirect gb_postcode_data; do
wget --output-document=/home/${NM_USER}/Nominatim/data/${f}.sql.bin http://www.nominatim.org/data/${f}.sql.bin
done
}
function setup_nm_apache(){
cat >/etc/apache2/conf-available/nominatim_dir.conf <<EOF
<Directory "/var/www/${PROJECT_NAME}/website">
Options FollowSymLinks MultiViews
AddType text/html .php
DirectoryIndex search.php
Require all granted
</Directory>
Alias /nominatim /var/www/${PROJECT_NAME}/website
EOF
a2enconf nominatim_dir.conf
su ${NM_USER} <<EOF
psql -d nominatim -c 'GRANT usage ON SCHEMA public TO "www-data"'
psql -d nominatim -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "www-data"'
EOF
systemctl restart apache2
}
function import_osm_data(){
pushd /home/${NM_USER}
#13. Loading data into your server
PBF_FILE="/home/${NM_USER}/${PBF_URL##*/}"
wget ${PBF_URL}
chown ${NM_USER}:${NM_USER} ${PBF_FILE}
UPDATE_URL="$(echo ${PBF_URL} | sed 's/latest.osm.pbf/updates/')"
cat >.env <<CAT_CMD
# update replication url
NOMINATIM_REPLICATION_URL="${UPDATE_URL}"
# How often upstream publishes diffs (in seconds)
NOMINATIM_REPLICATION_UPDATE_INTERVAL=86400
# How long to sleep if no update found yet (in seconds)
NOMINATIM_REPLICATION_RECHECK_INTERVAL=900
NOMINATIM_DATABASE_DSN="pgsql:dbname=nominatim;user=${NM_USER};password=${NM_PG_PASS}"
CAT_CMD
NP=$(grep -c 'model name' /proc/cpuinfo)
let AVAIL_MEM=$(free -m | grep -i 'mem:' | sed 's/[ \t]\+/ /g' | cut -f4,7 -d' ' | tr ' ' '+')
let C_MEM=(AVAIL_MEM/4)*3
mkdir /var/www/${PROJECT_NAME}
chown -R ${NM_USER}:${NM_USER} /var/www/${PROJECT_NAME}
su - ${NM_USER} <<EOF
wget -O /home/${NM_USER}/Nominatim/data/country_osm_grid.sql.gz http://www.nominatim.org/data/country_grid.sql.gz
nominatim import -j ${NP} --osm-file ${PBF_FILE} --osm2pgsql-cache ${C_MEM} --project-dir /var/www/${PROJECT_NAME} 2>&1 | tee /tmp/setup.log
EOF
rm -f ${PBF_FILE}
popd
chown -R www-data:www-data /var/www/${PROJECT_NAME}
}
function enable_nm_updates(){
pushd /home/${NM_USER}
nominatim replication --init
popd
cat >/etc/systemd/system/nominatim-updates.service <<EOF
[Unit]
Description=Nominatum Updates
Documentation=https://github.com/f1ana/OpenNameSearch
[Service]
Type=simple
User=${NM_USER}
Group=${NM_USER}
WorkingDirectory=/home/${NM_USER}/
ExecStart=nominatim replication
StandardOutput=append:/var/log/nominatim-updates.log
StandardError=append:/var/log/nominatim-updates.error.log
[Install]
WantedBy=multi-user.target
EOF
}
function install_housenumber(){
apt-get install -y python-gdal
#11Gb download!
mkdir -p /home/${NM_USER}/tiger/
wget -P/home/${NM_USER}/tiger/ ftp://mirror1.shellbot.com/census/geo/tiger/TIGER2015/EDGES/
su - ${NM_USER} <<EOF
cd /home/${NM_USER}/Nominatim/build
./utils/imports.php --parse-tiger /home/${NM_USER}/tiger
./utils/setup.php --import-tiger-data
EOF
}
#Check input parameters
if [ -z "${PBF_URL}" ]; then
echo "Usage: $0 <pbf_url>"; exit 1;
fi
touch /root/auth.txt
export DEBIAN_FRONTEND=noninteractive
apt-get -y update
setup_nm_user;
install_prerequisites;
install_postgresql;
install_nominatim;
install_nominatim_ui;
#download_optional_data; #uncomment if you want optional data. Adds 30Gb to install size
import_osm_data;
setup_nm_apache;
enable_nm_updates;
#install_housenumber; #uncomment to install Tiger census data.

View File

@ -1,2 +1,86 @@
# Nominatim-Server
# Nominatim Server
![OpenNameSearch](docs/Nominatim-Server.png)
* Project page: https://www.acugis.com/Nominatim-Server
* Documentation: https://nominatim-server.docs.acugis.com
[![Documentation Status](https://readthedocs.org/projects/opennamesearch/badge/?version=latest)](https://nominatim-server.docs.acugis.com/en/latest/?badge=latest)
This script is for building a Nominatim server with OpenStreetMap data.
Only for use on a clean Ubuntu 22!
Step 1: Get the Nominatim-Server.sh script from GitHub
wget https://raw.githubusercontent.com/AcuGIS/Nominatim-Server/master/Nominatim-Server.sh
Step 2: Make it executable:
chmod 755 Nominatim-Server.sh
Step 3: Run the script
## Script usage:
The script accepts a PBF url:
./Nominatim-Server.sh pbf_url
The pbf_url is the complete PBF url from GeoFarbrik
## Examples:
Load Andorra data from GeoFabrik (one of the smallest data sets, good for testing):
./Nominatim-Server.sh https://download.geofabrik.de/europe/andorra-latest.osm.pbf
## Welcome Page
Once installation completes, navigate to the IP or hostname on your server.
You should see a page as below:
![OpenNameSearch](docs/Nominatim-Server.png)
Click the Search function (or go to domain.com/search.html)
You should see a page as below:
![OpenNameSearch](docs/OpenNameSearch-Search.png)
To test functionality, enter below into the Search box::
AD500 Andorra la Vella, Andorra
Confirm that results are returned
![OpenNameSearch](docs/Search-Results.png)
## Loading Additional PBFs, Multiplie PBFs, or Replacing Existing PBFs:
You can use our reload-Nominatim-Server.sh script via GitHUB script.
Usage is:
<code>
./reload-Nominatim-Server.sh [PBF_URL1] ...
</code>
## Enable Automatic Updates
The script creates an updater service. In order to enable updates:
<code>
systemctl enable nominatim-updates.service
systemctl start nominatim-updates.service
</code>
## Credits
[Produced by AcuGIS. We Make GIS Simple](https://www.acugis.com)
[Cited, Inc. Wilmington, Delaware](https://citedcorp.com)

885
app/assets/css/style.css Normal file
View File

@ -0,0 +1,885 @@
/* Fonts */
:root {
--font-default: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-primary: "Montserrat", sans-serif;
--font-secondary: "Poppins", sans-serif;
}
/* Colors */
:root {
--color-default: #222222;
--color-primary: #008374;
--color-secondary: #f85a40;
}
/* Smooth scroll behavior */
:root {
scroll-behavior: smooth;
}
body {
font-family: var(--font-default);
color: var(--color-default);
}
a {
color: var(--color-primary);
text-decoration: none;
}
a:hover {
color: #00b6a1;
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-primary);
}
section {
padding: 60px 0;
overflow: hidden;
}
.sections-bg {
background-color: #f6f6f6;
}
.section-header {
text-align: center;
padding-bottom: 60px;
}
.section-header h2 {
font-size: 32px;
font-weight: 600;
margin-bottom: 20px;
padding-bottom: 20px;
position: relative;
}
.section-header h2:after {
content: "";
position: absolute;
display: block;
width: 50px;
height: 3px;
background: var(--color-primary);
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
.section-header p {
margin-bottom: 0;
color: #6f6f6f;
}
.breadcrumbs .page-header {
padding: 60px 0 60px 0;
min-height: 20vh;
position: relative;
background-color: var(--color-primary);
}
.breadcrumbs .page-header h2 {
font-size: 56px;
font-weight: 500;
color: #fff;
font-family: var(--font-secondary);
}
.breadcrumbs .page-header p {
color: rgba(255, 255, 255, 0.8);
}
.breadcrumbs nav {
background-color: #f6f6f6;
padding: 20px 0;
}
.breadcrumbs nav ol {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0;
font-size: 16px;
font-weight: 600;
color: var(--color-default);
}
.breadcrumbs nav ol a {
color: var(--color-primary);
transition: 0.3s;
}
.breadcrumbs nav ol a:hover {
text-decoration: underline;
}
.breadcrumbs nav ol li+li {
padding-left: 10px;
}
.breadcrumbs nav ol li+li::before {
display: inline-block;
padding-right: 10px;
color: var(--color-secondary);
content: "/";
}
.scroll-top {
position: fixed;
visibility: hidden;
opacity: 0;
right: 15px;
bottom: -15px;
z-index: 99999;
background: var(--color-secondary);
width: 44px;
height: 44px;
border-radius: 50px;
transition: all 0.4s;
}
.scroll-top i {
font-size: 24px;
color: #fff;
line-height: 0;
}
.scroll-top:hover {
background: rgba(248, 90, 64, 0.8);
color: #fff;
}
.scroll-top.active {
visibility: visible;
opacity: 1;
bottom: 15px;
}
#preloader {
position: fixed;
inset: 0;
z-index: 999999;
overflow: hidden;
background: #fff;
transition: all 0.6s ease-out;
}
#preloader:before {
content: "";
position: fixed;
top: calc(50% - 30px);
left: calc(50% - 30px);
border: 6px solid #fff;
border-color: var(--color-primary) transparent var(--color-primary) transparent;
border-radius: 50%;
width: 60px;
height: 60px;
animation: animate-preloader 1.5s linear infinite;
}
@keyframes animate-preloader {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@media screen and (max-width: 768px) {
[data-aos-delay] {
transition-delay: 0 !important;
}
}
.topbar {
background: #00796b;
height: 40px;
font-size: 14px;
transition: all 0.5s;
color: #fff;
padding: 0;
}
.topbar .contact-info i {
font-style: normal;
color: #fff;
line-height: 0;
}
.topbar .contact-info i a,
.topbar .contact-info i span {
padding-left: 5px;
color: #fff;
}
@media (max-width: 575px) {
.topbar .contact-info i a,
.topbar .contact-info i span {
font-size: 13px;
}
}
.topbar .contact-info i a {
line-height: 0;
transition: 0.3s;
}
.topbar .contact-info i a:hover {
color: #fff;
text-decoration: underline;
}
.topbar .social-links a {
color: rgba(255, 255, 255, 0.7);
line-height: 0;
transition: 0.3s;
margin-left: 20px;
}
.topbar .social-links a:hover {
color: #fff;
}
.header {
transition: all 0.5s;
z-index: 997;
height: 90px;
background-color: var(--color-primary);
}
.header.sticked {
position: fixed;
top: 0;
right: 0;
left: 0;
height: 70px;
box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.1);
}
.header .logo img {
max-height: 40px;
margin-right: 6px;
}
.header .logo h1 {
font-size: 30px;
margin: 0;
font-weight: 600;
letter-spacing: 0.8px;
color: #fff;
font-family: var(--font-primary);
}
.header .logo h1 span {
color: #f96f59;
}
.sticked-header-offset {
margin-top: 70px;
}
section {
scroll-margin-top: 70px;
}
@media (min-width: 1280px) {
.navbar {
padding: 0;
}
.navbar ul {
margin: 0;
padding: 0;
display: flex;
list-style: none;
align-items: center;
}
.navbar li {
position: relative;
}
.navbar>ul>li {
white-space: nowrap;
padding: 10px 0 10px 28px;
}
.navbar a,
.navbar a:focus {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 3px;
font-family: var(--font-secondary);
font-size: 16px;
font-weight: 600;
color: rgba(255, 255, 255, 0.6);
white-space: nowrap;
transition: 0.3s;
position: relative;
}
.navbar a i,
.navbar a:focus i {
font-size: 12px;
line-height: 0;
margin-left: 5px;
}
.navbar>ul>li>a:before {
content: "";
position: absolute;
width: 100%;
height: 2px;
bottom: -6px;
left: 0;
background-color: var(--color-secondary);
visibility: hidden;
width: 0px;
transition: all 0.3s ease-in-out 0s;
}
.navbar a:hover:before,
.navbar li:hover>a:before,
.navbar .active:before {
visibility: visible;
width: 100%;
}
.navbar a:hover,
.navbar .active,
.navbar .active:focus,
.navbar li:hover>a {
color: #fff;
}
.navbar .dropdown ul {
display: block;
position: absolute;
left: 28px;
top: calc(100% + 30px);
margin: 0;
padding: 10px 0;
z-index: 99;
opacity: 0;
visibility: hidden;
background: #fff;
box-shadow: 0px 0px 30px rgba(127, 137, 161, 0.25);
transition: 0.3s;
border-radius: 4px;
}
.navbar .dropdown ul li {
min-width: 200px;
}
.navbar .dropdown ul a {
padding: 10px 20px;
font-size: 15px;
text-transform: none;
font-weight: 600;
color: #006a5d;
}
.navbar .dropdown ul a i {
font-size: 12px;
}
.navbar .dropdown ul a:hover,
.navbar .dropdown ul .active:hover,
.navbar .dropdown ul li:hover>a {
color: var(--color-secondary);
}
.navbar .dropdown:hover>ul {
opacity: 1;
top: 100%;
visibility: visible;
}
.navbar .dropdown .dropdown ul {
top: 0;
left: calc(100% - 30px);
visibility: hidden;
}
.navbar .dropdown .dropdown:hover>ul {
opacity: 1;
top: 0;
left: 100%;
visibility: visible;
}
}
@media (min-width: 1280px) and (max-width: 1366px) {
.navbar .dropdown .dropdown ul {
left: -90%;
}
.navbar .dropdown .dropdown:hover>ul {
left: -100%;
}
}
@media (min-width: 1280px) {
.mobile-nav-show,
.mobile-nav-hide {
display: none;
}
}
@media (max-width: 1279px) {
.navbar {
position: fixed;
top: 0;
right: -100%;
width: 100%;
max-width: 400px;
bottom: 0;
transition: 0.3s;
z-index: 9997;
}
.navbar ul {
position: absolute;
inset: 0;
padding: 50px 0 10px 0;
margin: 0;
background: rgba(0, 131, 116, 0.9);
overflow-y: auto;
transition: 0.3s;
z-index: 9998;
}
.navbar a,
.navbar a:focus {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
font-family: var(--font-primary);
font-size: 15px;
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
white-space: nowrap;
transition: 0.3s;
}
.navbar a i,
.navbar a:focus i {
font-size: 12px;
line-height: 0;
margin-left: 5px;
}
.navbar a:hover,
.navbar .active,
.navbar .active:focus,
.navbar li:hover>a {
color: #fff;
}
.navbar .getstarted,
.navbar .getstarted:focus {
background: var(--color-primary);
padding: 8px 20px;
border-radius: 4px;
margin: 15px;
color: #fff;
}
.navbar .getstarted:hover,
.navbar .getstarted:focus:hover {
color: #fff;
background: rgba(0, 131, 116, 0.8);
}
.navbar .dropdown ul,
.navbar .dropdown .dropdown ul {
position: static;
display: none;
padding: 10px 0;
margin: 10px 20px;
transition: all 0.5s ease-in-out;
background-color: #007466;
border: 1px solid #006459;
}
.navbar .dropdown>.dropdown-active,
.navbar .dropdown .dropdown>.dropdown-active {
display: block;
}
.mobile-nav-show {
color: rgba(255, 255, 255, 0.6);
font-size: 28px;
cursor: pointer;
line-height: 0;
transition: 0.5s;
z-index: 9999;
margin-right: 10px;
}
.mobile-nav-hide {
color: #fff;
font-size: 32px;
cursor: pointer;
line-height: 0;
transition: 0.5s;
position: fixed;
right: 20px;
top: 20px;
z-index: 9999;
}
.mobile-nav-active {
overflow: hidden;
}
.mobile-nav-active .navbar {
right: 0;
}
.mobile-nav-active .navbar:before {
content: "";
position: fixed;
inset: 0;
background: rgba(0, 106, 93, 0.8);
z-index: 9996;
}
}
.faq .content h3 {
font-weight: 400;
font-size: 34px;
}
.faq .content h4 {
font-size: 20px;
font-weight: 700;
margin-top: 5px;
}
.faq .content p {
font-size: 15px;
color: #6c757d;
}
.faq .accordion-item {
border: 0;
margin-bottom: 20px;
box-shadow: 0px 5px 25px 0px rgba(0, 0, 0, 0.06);
border-radius: 10px;
}
.faq .accordion-item:last-child {
margin-bottom: 0;
}
.faq .accordion-collapse {
border: 0;
}
.faq .accordion-button {
padding: 20px 50px 20px 20px;
font-weight: 600;
border: 0;
font-size: 18px;
line-height: 24px;
color: var(--color-default);
text-align: left;
background: #fff;
box-shadow: none;
border-radius: 10px;
}
.faq .accordion-button .num {
padding-right: 10px;
font-size: 20px;
line-height: 0;
color: var(--color-primary);
}
.faq .accordion-button:not(.collapsed) {
color: var(--color-primary);
border-bottom: 0;
box-shadow: none;
}
.faq .accordion-button:after {
position: absolute;
right: 20px;
top: 20px;
}
.faq .accordion-body {
padding: 0 40px 30px 45px;
border: 0;
border-radius: 10px;
background: #fff;
box-shadow: none;
}
.hero {
width: 100%;
position: relative;
background: var(--color-primary);
padding: 60px 0 0 0;
}
@media (min-width: 1365px) {
.hero {
background-attachment: fixed;
}
}
.hero h2 {
font-size: 48px;
font-weight: 700;
margin-bottom: 20px;
color: #fff;
}
.hero p {
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
margin-bottom: 30px;
}
.hero .btn-get-started {
font-family: var(--font-primary);
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
display: inline-block;
padding: 14px 40px;
border-radius: 50px;
transition: 0.3s;
color: #fff;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.08);
border: 2px solid rgba(255, 255, 255, 0.1);
}
.hero .btn-get-started:hover {
border-color: rgba(255, 255, 255, 0.5);
}
.hero .btn-watch-video {
font-size: 16px;
transition: 0.5s;
margin-left: 25px;
color: #fff;
font-weight: 600;
}
.hero .btn-watch-video i {
color: rgba(255, 255, 255, 0.5);
font-size: 32px;
transition: 0.3s;
line-height: 0;
margin-right: 8px;
}
.hero .btn-watch-video:hover i {
color: #fff;
}
@media (max-width: 640px) {
.hero h2 {
font-size: 36px;
}
.hero .btn-get-started,
.hero .btn-watch-video {
font-size: 14px;
}
}
.hero .icon-boxes {
padding-bottom: 60px;
}
@media (min-width: 1200px) {
.hero .icon-boxes:before {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: calc(50% + 20px);
background-color: #fff;
}
}
.hero .icon-box {
padding: 60px 30px;
position: relative;
overflow: hidden;
background: #008d7d;
box-shadow: 0 0 29px 0 rgba(0, 0, 0, 0.08);
transition: all 0.3s ease-in-out;
border-radius: 8px;
z-index: 1;
height: 100%;
width: 100%;
text-align: center;
}
.hero .icon-box .title {
font-weight: 700;
margin-bottom: 15px;
font-size: 24px;
}
.hero .icon-box .title a {
color: #fff;
transition: 0.3s;
}
.hero .icon-box .icon {
margin-bottom: 20px;
padding-top: 10px;
display: inline-block;
transition: all 0.3s ease-in-out;
font-size: 48px;
line-height: 1;
color: rgba(255, 255, 255, 0.6);
}
.hero .icon-box:hover {
background: #009786;
}
.hero .icon-box:hover .title a,
.hero .icon-box:hover .icon {
color: #fff;
}
.footer {
font-size: 14px;
background-color: var(--color-primary);
padding: 50px 0;
color: white;
}
.footer .footer-info .logo {
line-height: 0;
margin-bottom: 25px;
}
.footer .footer-info .logo img {
max-height: 40px;
margin-right: 6px;
}
.footer .footer-info .logo span {
font-size: 30px;
font-weight: 700;
letter-spacing: 1px;
color: #fff;
font-family: var(--font-primary);
}
.footer .footer-info p {
font-size: 14px;
font-family: var(--font-primary);
}
.footer .social-links a {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.2);
font-size: 16px;
color: rgba(255, 255, 255, 0.7);
margin-right: 10px;
transition: 0.3s;
}
.footer .social-links a:hover {
color: #fff;
border-color: #fff;
}
.footer h4 {
font-size: 16px;
font-weight: bold;
position: relative;
padding-bottom: 12px;
}
.footer .footer-links {
margin-bottom: 30px;
}
.footer .footer-links ul {
list-style: none;
padding: 0;
margin: 0;
}
.footer .footer-links ul i {
padding-right: 2px;
color: rgba(0, 131, 116, 0.8);
font-size: 12px;
line-height: 0;
}
.footer .footer-links ul li {
padding: 10px 0;
display: flex;
align-items: center;
}
.footer .footer-links ul li:first-child {
padding-top: 0;
}
.footer .footer-links ul a {
color: rgba(255, 255, 255, 0.7);
transition: 0.3s;
display: inline-block;
line-height: 1;
}
.footer .footer-links ul a:hover {
color: #fff;
}
.footer .footer-contact p {
line-height: 26px;
}
.footer .copyright {
text-align: center;
}
.footer .credits {
padding-top: 4px;
text-align: center;
font-size: 13px;
}
.footer .credits a {
color: #fff;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
app/assets/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
app/assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
app/assets/img/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

244
app/assets/js/web.js Normal file
View File

@ -0,0 +1,244 @@
document.addEventListener('DOMContentLoaded', () => {
"use strict";
const preloader = document.querySelector('#preloader');
if (preloader) {
window.addEventListener('load', () => {
preloader.remove();
});
}
const selectHeader = document.querySelector('#header');
if (selectHeader) {
let headerOffset = selectHeader.offsetTop;
let nextElement = selectHeader.nextElementSibling;
const headerFixed = () => {
if ((headerOffset - window.scrollY) <= 0) {
selectHeader.classList.add('sticked');
if (nextElement) nextElement.classList.add('sticked-header-offset');
} else {
selectHeader.classList.remove('sticked');
if (nextElement) nextElement.classList.remove('sticked-header-offset');
}
}
window.addEventListener('load', headerFixed);
document.addEventListener('scroll', headerFixed);
}
let navbarlinks = document.querySelectorAll('#navbar a');
function navbarlinksActive() {
navbarlinks.forEach(navbarlink => {
if (!navbarlink.hash) return;
let section = document.querySelector(navbarlink.hash);
if (!section) return;
let position = window.scrollY + 200;
if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) {
navbarlink.classList.add('active');
} else {
navbarlink.classList.remove('active');
}
})
}
window.addEventListener('load', navbarlinksActive);
document.addEventListener('scroll', navbarlinksActive);
const mobileNavShow = document.querySelector('.mobile-nav-show');
const mobileNavHide = document.querySelector('.mobile-nav-hide');
document.querySelectorAll('.mobile-nav-toggle').forEach(el => {
el.addEventListener('click', function(event) {
event.preventDefault();
mobileNavToogle();
})
});
function mobileNavToogle() {
document.querySelector('body').classList.toggle('mobile-nav-active');
mobileNavShow.classList.toggle('d-none');
mobileNavHide.classList.toggle('d-none');
}
document.querySelectorAll('#navbar a').forEach(navbarlink => {
if (!navbarlink.hash) return;
let section = document.querySelector(navbarlink.hash);
if (!section) return;
navbarlink.addEventListener('click', () => {
if (document.querySelector('.mobile-nav-active')) {
mobileNavToogle();
}
});
});
const navDropdowns = document.querySelectorAll('.navbar .dropdown > a');
navDropdowns.forEach(el => {
el.addEventListener('click', function(event) {
if (document.querySelector('.mobile-nav-active')) {
event.preventDefault();
this.classList.toggle('active');
this.nextElementSibling.classList.toggle('dropdown-active');
let dropDownIndicator = this.querySelector('.dropdown-indicator');
dropDownIndicator.classList.toggle('bi-chevron-up');
dropDownIndicator.classList.toggle('bi-chevron-down');
}
})
});
const glightbox = GLightbox({
selector: '.glightbox'
});
const scrollTop = document.querySelector('.scroll-top');
if (scrollTop) {
const togglescrollTop = function() {
window.scrollY > 100 ? scrollTop.classList.add('active') : scrollTop.classList.remove('active');
}
window.addEventListener('load', togglescrollTop);
document.addEventListener('scroll', togglescrollTop);
scrollTop.addEventListener('click', window.scrollTo({
top: 0,
behavior: 'smooth'
}));
}
new PureCounter();
new Swiper('.clients-slider', {
speed: 400,
loop: true,
autoplay: {
delay: 5000,
disableOnInteraction: false
},
slidesPerView: 'auto',
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true
},
breakpoints: {
320: {
slidesPerView: 2,
spaceBetween: 40
},
480: {
slidesPerView: 3,
spaceBetween: 60
},
640: {
slidesPerView: 4,
spaceBetween: 80
},
992: {
slidesPerView: 6,
spaceBetween: 120
}
}
});
new Swiper('.slides-1', {
speed: 600,
loop: true,
autoplay: {
delay: 5000,
disableOnInteraction: false
},
slidesPerView: 'auto',
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
});
new Swiper('.slides-3', {
speed: 600,
loop: true,
autoplay: {
delay: 5000,
disableOnInteraction: false
},
slidesPerView: 'auto',
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
breakpoints: {
320: {
slidesPerView: 1,
spaceBetween: 40
},
1200: {
slidesPerView: 3,
}
}
});
let portfolionIsotope = document.querySelector('.portfolio-isotope');
if (portfolionIsotope) {
let portfolioFilter = portfolionIsotope.getAttribute('data-portfolio-filter') ? portfolionIsotope.getAttribute('data-portfolio-filter') : '*';
let portfolioLayout = portfolionIsotope.getAttribute('data-portfolio-layout') ? portfolionIsotope.getAttribute('data-portfolio-layout') : 'masonry';
let portfolioSort = portfolionIsotope.getAttribute('data-portfolio-sort') ? portfolionIsotope.getAttribute('data-portfolio-sort') : 'original-order';
window.addEventListener('load', () => {
let portfolioIsotope = new Isotope(document.querySelector('.portfolio-container'), {
itemSelector: '.portfolio-item',
layoutMode: portfolioLayout,
filter: portfolioFilter,
sortBy: portfolioSort
});
let menuFilters = document.querySelectorAll('.portfolio-isotope .portfolio-flters li');
menuFilters.forEach(function(el) {
el.addEventListener('click', function() {
document.querySelector('.portfolio-isotope .portfolio-flters .filter-active').classList.remove('filter-active');
this.classList.add('filter-active');
portfolioIsotope.arrange({
filter: this.getAttribute('data-filter')
});
if (typeof aos_init === 'function') {
aos_init();
}
}, false);
});
});
}
function aos_init() {
AOS.init({
duration: 1000,
easing: 'ease-in-out',
once: true,
mirror: false
});
}
window.addEventListener('load', () => {
aos_init();
});
});

885
app/assets/style.css Normal file
View File

@ -0,0 +1,885 @@
/* Fonts */
:root {
--font-default: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-primary: "Montserrat", sans-serif;
--font-secondary: "Poppins", sans-serif;
}
/* Colors */
:root {
--color-default: #222222;
--color-primary: #008374;
--color-secondary: #f85a40;
}
/* Smooth scroll behavior */
:root {
scroll-behavior: smooth;
}
body {
font-family: var(--font-default);
color: var(--color-default);
}