updated store assets

This commit is contained in:
Hadrian Burkhardt
2026-07-09 17:23:54 +02:00
parent 73f6373b84
commit 10d9825191
36 changed files with 2097 additions and 171 deletions
Binary file not shown.
+5 -1
View File
@@ -1,5 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+191 -119
View File
@@ -1,78 +1,128 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,96 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
if $JAVACMD --add-opens java.base/java.lang=ALL-UNNAMED -version ; then
DEFAULT_JVM_OPTS="--add-opens java.base/java.lang=ALL-UNNAMED $DEFAULT_JVM_OPTS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+49 -51
View File
@@ -1,84 +1,82 @@
@if "%DEBUG%" == "" @echo off
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TEMPLATE="$ROOT_DIR/store-assets/google-ads/google-ads-creatives.html"
OUT_DIR="$ROOT_DIR/store-assets/google-ads"
mkdir -p "$OUT_DIR"
creatives=(
"pqs-square-01-scanner 1200 1200"
"pqs-square-02-results 1200 1200"
"pqs-square-03-risk 1200 1200"
"pqs-square-04-image-scan 1200 1200"
"pqs-square-05-contact 1200 1200"
"pqs-square-06-history 1200 1200"
"pqs-square-07-batch 1200 1200"
"pqs-square-08-settings 1200 1200"
"pqs-horizontal-01-scanner-results 1200 628"
"pqs-horizontal-02-gallery 1200 628"
"pqs-horizontal-03-risk 1200 628"
"pqs-horizontal-04-history 1200 628"
"pqs-horizontal-05-wifi 1200 628"
"pqs-horizontal-06-event 1200 628"
"pqs-horizontal-07-tablet 1200 628"
"pqs-horizontal-08-settings 1200 628"
"pqs-vertical-01-scanner 1200 1500"
"pqs-vertical-02-gallery 1200 1500"
"pqs-vertical-03-event 1200 1500"
"pqs-vertical-04-privacy 1200 1500"
)
for creative in "${creatives[@]}"; do
read -r id width height <<< "$creative"
google-chrome \
--headless \
--disable-gpu \
--no-sandbox \
--hide-scrollbars \
--allow-file-access-from-files \
--window-size="${width},${height}" \
--screenshot="$OUT_DIR/${id}.png" \
"file://$TEMPLATE?creative=$id" >/dev/null
printf '%s %sx%s\n' "$id" "$width" "$height"
done
@@ -0,0 +1,862 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Private QR Scanner Google Ads Creatives</title>
<style>
:root {
color-scheme: light;
--deep: #07111f;
--navy: #0b1220;
--teal-900: #123b3f;
--teal-800: #155e63;
--teal-700: #0f766e;
--teal-300: #2dd4bf;
--mint: #dff7f2;
--bg: #f6fbfa;
--surface: #ffffff;
--soft-blue: #f2f7ff;
--text: #0b1220;
--muted: #607080;
--line: #dce8e6;
--amber: #ffc857;
--coral: #ff6b5f;
--sky: #55b9ff;
--violet: #8b7cf6;
}
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: #ffffff;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#stage {
position: relative;
overflow: hidden;
width: var(--w);
height: var(--h);
isolation: isolate;
background: var(--base);
}
.band,
.panel,
.rail,
.tile-field,
.scan-beam,
.code-stack,
.ticket-strip,
.wifi-mark,
.shield,
.chips,
.history-steps,
.grid-plate,
.device,
.tablet {
position: absolute;
}
.band {
inset: auto -8% -18% -8%;
height: 44%;
background: var(--band);
transform: skewY(-7deg);
transform-origin: center;
z-index: 0;
}
.band.top {
inset: -20% -8% auto -8%;
height: 42%;
transform: skewY(-6deg);
}
.rail {
background:
linear-gradient(90deg, transparent 0 12%, rgba(255,255,255,.22) 12% 13%, transparent 13% 28%, rgba(255,255,255,.2) 28% 29%, transparent 29%),
linear-gradient(135deg, rgba(45,212,191,.2), rgba(85,185,255,.08));
border: 1px solid rgba(255,255,255,.16);
transform: rotate(var(--r));
border-radius: 0;
z-index: 0;
}
.panel {
border-radius: 22px;
background: color-mix(in srgb, var(--panel-bg), transparent 0%);
border: 1px solid var(--panel-border);
box-shadow: 0 28px 70px rgba(7, 17, 31, .16);
z-index: 1;
}
.tile-field {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-auto-rows: 1fr;
gap: 7px;
opacity: .7;
transform: rotate(var(--r, 0deg));
z-index: 0;
}
.tile-field span {
border-radius: 2px;
background: var(--tile);
opacity: var(--o, .75);
}
.scan-beam {
height: 8px;
background: linear-gradient(90deg, transparent, var(--teal-300), transparent);
box-shadow: 0 0 34px rgba(45, 212, 191, .95);
transform: rotate(var(--r, 0deg));
z-index: 4;
}
.code-stack {
display: grid;
gap: 16px;
z-index: 2;
}
.code-card {
height: 74px;
border-radius: 14px;
background: rgba(255, 255, 255, .88);
border: 1px solid rgba(96, 112, 128, .14);
box-shadow: 0 18px 44px rgba(7, 17, 31, .12);
overflow: hidden;
position: relative;
}
.code-card::before,
.code-card::after {
content: "";
position: absolute;
left: 18px;
right: 18px;
height: 10px;
border-radius: 99px;
background: var(--line);
}
.code-card::before {
top: 18px;
width: 54%;
}
.code-card::after {
top: 40px;
width: 78%;
background: color-mix(in srgb, var(--teal-300), white 70%);
}
.ticket-strip {
display: grid;
gap: 18px;
transform: rotate(var(--r, 0deg));
z-index: 1;
}
.ticket {
width: 272px;
height: 96px;
border-radius: 16px;
background:
linear-gradient(90deg, rgba(7,17,31,.12) 0 2px, transparent 2px 16px),
var(--surface);
border: 1px solid rgba(96,112,128,.18);
box-shadow: 0 20px 48px rgba(7,17,31,.16);
position: relative;
overflow: hidden;
}
.ticket::before {
content: "";
position: absolute;
inset: 20px auto 20px 24px;
width: 54px;
background:
linear-gradient(var(--text) 0 0) 0 0 / 16px 16px,
linear-gradient(var(--text) 0 0) 26px 0 / 16px 16px,
linear-gradient(var(--text) 0 0) 0 26px / 16px 16px,
linear-gradient(var(--teal-300) 0 0) 26px 26px / 16px 16px;
background-repeat: no-repeat;
}
.ticket::after {
content: "";
position: absolute;
left: 96px;
top: 24px;
width: 132px;
height: 42px;
border-radius: 8px;
background:
linear-gradient(var(--line), var(--line)) 0 0 / 100% 10px,
linear-gradient(color-mix(in srgb, var(--teal-300), white 70%), color-mix(in srgb, var(--teal-300), white 70%)) 0 28px / 70% 10px;
background-repeat: no-repeat;
}
.wifi-mark {
width: var(--s);
height: var(--s);
z-index: 1;
}
.wifi-mark::before,
.wifi-mark::after,
.wifi-dot {
content: "";
position: absolute;
border: solid var(--wifi-color);
border-width: 10px 10px 0 0;
border-radius: 0 100% 0 0;
transform: rotate(-45deg);
transform-origin: 100% 100%;
opacity: .8;
}
.wifi-mark::before {
inset: 0 0 auto auto;
width: 100%;
height: 100%;
}
.wifi-mark::after {
inset: 28% 28% auto auto;
width: 44%;
height: 44%;
}
.wifi-dot {
right: 0;
bottom: 0;
width: 18%;
height: 18%;
background: var(--wifi-color);
border: 0;
border-radius: 50%;
transform: none;
}
.shield {
width: var(--s);
height: calc(var(--s) * 1.25);
background: color-mix(in srgb, var(--shield-fill), white 10%);
clip-path: polygon(50% 0, 100% 18%, 100% 55%, 50% 100%, 0 55%, 0 18%);
opacity: .9;
box-shadow: 0 28px 60px rgba(7,17,31,.18);
z-index: 1;
}
.shield::after {
content: "";
position: absolute;
left: 30%;
top: 30%;
width: 40%;
height: 26%;
border: solid var(--navy);
border-width: 0 0 14px 14px;
transform: rotate(-45deg);
}
.chips {
display: grid;
gap: 12px;
z-index: 1;
}
.chip {
width: var(--chip-w);
height: 44px;
border-radius: 12px;
background: var(--chip-bg);
box-shadow: 0 14px 34px rgba(7,17,31,.12);
border: 1px solid rgba(255,255,255,.48);
}
.history-steps {
display: grid;
gap: 18px;
z-index: 1;
}
.step {
width: var(--step-w);
height: 64px;
border-radius: 14px;
background: rgba(255,255,255,.9);
border: 1px solid rgba(96,112,128,.16);
box-shadow: 0 18px 44px rgba(7,17,31,.12);
position: relative;
}
.step::before {
content: "";
position: absolute;
left: 18px;
top: 18px;
width: 28px;
height: 28px;
border-radius: 8px;
background:
linear-gradient(var(--navy) 0 0) 6px 6px / 8px 8px,
linear-gradient(var(--navy) 0 0) 18px 6px / 8px 8px,
linear-gradient(var(--teal-300) 0 0) 6px 18px / 8px 8px,
linear-gradient(var(--navy) 0 0) 18px 18px / 8px 8px;
background-repeat: no-repeat;
}
.step::after {
content: "";
position: absolute;
left: 62px;
top: 21px;
width: 52%;
height: 22px;
border-radius: 99px;
background: var(--line);
box-shadow: 0 28px 0 -6px color-mix(in srgb, var(--teal-300), white 72%);
}
.grid-plate {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-auto-rows: 1fr;
gap: 8px;
padding: 18px;
border-radius: 22px;
background: rgba(255,255,255,.82);
border: 1px solid rgba(96,112,128,.12);
box-shadow: 0 26px 64px rgba(7,17,31,.14);
z-index: 1;
}
.grid-plate span {
border-radius: 4px;
background: var(--text);
}
.grid-plate span:nth-child(5n),
.grid-plate span:nth-child(8n) {
background: var(--teal-300);
}
.device {
width: var(--dw);
aspect-ratio: 1080 / 1920;
padding: var(--bezel, 15px);
border-radius: var(--radius, 46px);
background:
linear-gradient(150deg, rgba(255,255,255,.28), transparent 18%),
linear-gradient(180deg, #111827, #030712);
box-shadow:
0 42px 90px rgba(7, 17, 31, .34),
inset 0 0 0 1px rgba(255, 255, 255, .18);
transform: rotate(var(--r, 0deg));
z-index: 3;
}
.device::before {
content: "";
position: absolute;
top: 9px;
left: 50%;
width: 23%;
height: 12px;
border-radius: 99px;
background: #020617;
transform: translateX(-50%);
z-index: 2;
}
.device img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: calc(var(--radius, 46px) - 12px);
background: #07111f;
}
.tablet {
width: var(--tw);
aspect-ratio: 1440 / 2560;
padding: 18px;
border-radius: 34px;
background: linear-gradient(180deg, #111827, #030712);
box-shadow:
0 42px 90px rgba(7, 17, 31, .3),
inset 0 0 0 1px rgba(255,255,255,.16);
transform: rotate(var(--r, 0deg));
z-index: 2;
}
.tablet img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: 20px;
background: #07111f;
}
.soft-light {
position: absolute;
inset: 0;
background:
linear-gradient(120deg, rgba(255,255,255,.32), transparent 26%),
linear-gradient(0deg, rgba(255,255,255,.12), transparent 40%);
mix-blend-mode: soft-light;
z-index: 5;
pointer-events: none;
}
.fallback-list {
padding: 28px;
font: 20px/1.4 system-ui, sans-serif;
color: #111827;
}
</style>
</head>
<body>
<main id="stage"></main>
<script>
const screenshot = (kind, n) => `../private-qr-scanner-${kind}-screenshot-${n}.png`;
const creatives = [
{
id: "pqs-square-01-scanner",
size: [1200, 1200],
theme: ["#07111f", "#123b3f", "#dff7f2", "rgba(255,255,255,.12)", "#2dd4bf"],
elements: [
{ type: "band", cls: "top" },
{ type: "tileField", x: 64, y: 700, w: 300, h: 300, r: -8, tile: "rgba(223,247,242,.72)" },
{ type: "gridPlate", x: 756, y: 128, w: 264, h: 264 },
{ type: "beam", x: 188, y: 548, w: 824, r: -2 },
{ type: "phone", img: screenshot("phone", 1), x: 392, y: 94, w: 410, r: 0 }
]
},
{
id: "pqs-square-02-results",
size: [1200, 1200],
theme: ["#f6fbfa", "#dff7f2", "#ffffff", "rgba(21,94,99,.16)", "#0f766e"],
elements: [
{ type: "band" },
{ type: "codeStack", x: 104, y: 184, w: 374, count: 4 },
{ type: "chips", x: 770, y: 210, chipW: 250, colors: ["#dff7f2", "#f2f7ff", "#ffffff"] },
{ type: "phone", img: screenshot("phone", 2), x: 392, y: 86, w: 414, r: 0 }
]
},
{
id: "pqs-square-03-risk",
size: [1200, 1200],
theme: ["#101722", "#2b2f42", "#ffffff", "rgba(255,200,87,.24)", "#ffc857"],
elements: [
{ type: "rail", x: -110, y: 210, w: 1440, h: 180, r: -18 },
{ type: "shield", x: 764, y: 654, s: 220, fill: "#ffc857" },
{ type: "tileField", x: 770, y: 112, w: 260, h: 260, r: 7, tile: "rgba(255,200,87,.78)" },
{ type: "phone", img: screenshot("phone", 3), x: 384, y: 84, w: 430, r: 0 }
]
},
{
id: "pqs-square-04-image-scan",
size: [1200, 1200],
theme: ["#eef7ff", "#d7efff", "#ffffff", "rgba(85,185,255,.2)", "#55b9ff"],
elements: [
{ type: "band", cls: "top" },
{ type: "panel", x: 118, y: 712, w: 452, h: 264 },
{ type: "gridPlate", x: 748, y: 178, w: 260, h: 260 },
{ type: "beam", x: 184, y: 602, w: 820, r: 4 },
{ type: "phone", img: screenshot("phone", 4), x: 390, y: 88, w: 420, r: 0 }
]
},
{
id: "pqs-square-05-contact",
size: [1200, 1200],
theme: ["#fff7f3", "#ffe3dc", "#ffffff", "rgba(255,107,95,.18)", "#ff6b5f"],
elements: [
{ type: "band" },
{ type: "chips", x: 136, y: 192, chipW: 288, colors: ["#ffffff", "#ffe3dc", "#ffffff", "#dff7f2"] },
{ type: "codeStack", x: 744, y: 698, w: 330, count: 3 },
{ type: "phone", img: screenshot("phone", 5), x: 390, y: 84, w: 418, r: 0 }
]
},
{
id: "pqs-square-06-history",
size: [1200, 1200],
theme: ["#f7f5ff", "#e9e4ff", "#ffffff", "rgba(139,124,246,.2)", "#8b7cf6"],
elements: [
{ type: "band", cls: "top" },
{ type: "history", x: 104, y: 228, w: 330, count: 4 },
{ type: "tileField", x: 790, y: 690, w: 250, h: 250, r: -4, tile: "rgba(139,124,246,.55)" },
{ type: "phone", img: screenshot("phone", 6), x: 392, y: 82, w: 416, r: 0 }
]
},
{
id: "pqs-square-07-batch",
size: [1200, 1200],
theme: ["#07111f", "#0f766e", "#dff7f2", "rgba(45,212,191,.16)", "#2dd4bf"],
elements: [
{ type: "rail", x: -120, y: 802, w: 1450, h: 186, r: 16 },
{ type: "ticketStrip", x: 104, y: 182, count: 3, r: -4 },
{ type: "beam", x: 220, y: 576, w: 760, r: 0 },
{ type: "phone", img: screenshot("phone", 7), x: 392, y: 86, w: 416, r: 0 }
]
},
{
id: "pqs-square-08-settings",
size: [1200, 1200],
theme: ["#ffffff", "#f2f7ff", "#ffffff", "rgba(96,112,128,.14)", "#155e63"],
elements: [
{ type: "band" },
{ type: "shield", x: 142, y: 198, s: 210, fill: "#dff7f2" },
{ type: "chips", x: 778, y: 692, chipW: 276, colors: ["#dff7f2", "#f2f7ff", "#ffffff"] },
{ type: "phone", img: screenshot("phone", 8), x: 394, y: 86, w: 412, r: 0 }
]
},
{
id: "pqs-horizontal-01-scanner-results",
size: [1200, 628],
theme: ["#07111f", "#123b3f", "#dff7f2", "rgba(255,255,255,.13)", "#2dd4bf"],
elements: [
{ type: "band" },
{ type: "tileField", x: 88, y: 104, w: 248, h: 248, r: -5, tile: "rgba(223,247,242,.68)" },
{ type: "codeStack", x: 790, y: 124, w: 318, count: 3 },
{ type: "beam", x: 108, y: 310, w: 984, r: 0 },
{ type: "phone", img: screenshot("phone", 1), x: 382, y: 32, w: 256, r: 0 },
{ type: "phone", img: screenshot("phone", 2), x: 568, y: 74, w: 216, r: 0 }
]
},
{
id: "pqs-horizontal-02-gallery",
size: [1200, 628],
theme: ["#eef7ff", "#d7efff", "#ffffff", "rgba(85,185,255,.2)", "#55b9ff"],
elements: [
{ type: "rail", x: -120, y: 86, w: 1440, h: 154, r: -10 },
{ type: "panel", x: 742, y: 120, w: 326, h: 286 },
{ type: "gridPlate", x: 128, y: 158, w: 214, h: 214 },
{ type: "phone", img: screenshot("phone", 4), x: 446, y: 34, w: 254, r: 0 }
]
},
{
id: "pqs-horizontal-03-risk",
size: [1200, 628],
theme: ["#101722", "#2b2f42", "#ffffff", "rgba(255,200,87,.22)", "#ffc857"],
elements: [
{ type: "band", cls: "top" },
{ type: "shield", x: 824, y: 216, s: 176, fill: "#ffc857" },
{ type: "tileField", x: 118, y: 148, w: 214, h: 214, r: 8, tile: "rgba(255,200,87,.7)" },
{ type: "phone", img: screenshot("phone", 3), x: 466, y: 34, w: 248, r: 0 }
]
},
{
id: "pqs-horizontal-04-history",
size: [1200, 628],
theme: ["#f7f5ff", "#e9e4ff", "#ffffff", "rgba(139,124,246,.2)", "#8b7cf6"],
elements: [
{ type: "band" },
{ type: "history", x: 112, y: 140, w: 306, count: 3 },
{ type: "chips", x: 788, y: 158, chipW: 300, colors: ["#ffffff", "#e9e4ff", "#dff7f2"] },
{ type: "phone", img: screenshot("phone", 6), x: 474, y: 34, w: 246, r: 0 }
]
},
{
id: "pqs-horizontal-05-wifi",
size: [1200, 628],
theme: ["#ffffff", "#dff7f2", "#ffffff", "rgba(15,118,110,.18)", "#0f766e"],
elements: [
{ type: "rail", x: -116, y: 390, w: 1432, h: 154, r: 9 },
{ type: "wifi", x: 150, y: 164, s: 198, color: "#0f766e" },
{ type: "gridPlate", x: 828, y: 172, w: 206, h: 206 },
{ type: "phone", img: screenshot("phone", 5), x: 466, y: 34, w: 250, r: 0 }
]
},
{
id: "pqs-horizontal-06-event",
size: [1200, 628],
theme: ["#07111f", "#123b3f", "#dff7f2", "rgba(45,212,191,.16)", "#2dd4bf"],
elements: [
{ type: "ticketStrip", x: 102, y: 118, count: 3, r: -3 },
{ type: "beam", x: 196, y: 306, w: 806, r: 0 },
{ type: "phone", img: screenshot("phone", 7), x: 474, y: 34, w: 250, r: 0 },
{ type: "ticketStrip", x: 842, y: 152, count: 2, r: 5 }
]
},
{
id: "pqs-horizontal-07-tablet",
size: [1200, 628],
theme: ["#f6fbfa", "#dff7f2", "#ffffff", "rgba(21,94,99,.16)", "#155e63"],
elements: [
{ type: "band", cls: "top" },
{ type: "codeStack", x: 108, y: 144, w: 330, count: 3 },
{ type: "tablet", img: screenshot("7in-tablet", 2), x: 492, y: 18, w: 258, r: 0 },
{ type: "phone", img: screenshot("phone", 1), x: 712, y: 62, w: 212, r: 0 }
]
},
{
id: "pqs-horizontal-08-settings",
size: [1200, 628],
theme: ["#ffffff", "#f2f7ff", "#ffffff", "rgba(96,112,128,.14)", "#607080"],
elements: [
{ type: "band" },
{ type: "shield", x: 154, y: 168, s: 176, fill: "#dff7f2" },
{ type: "chips", x: 796, y: 156, chipW: 290, colors: ["#ffffff", "#dff7f2", "#f2f7ff"] },
{ type: "phone", img: screenshot("phone", 8), x: 474, y: 34, w: 246, r: 0 }
]
},
{
id: "pqs-vertical-01-scanner",
size: [1200, 1500],
theme: ["#07111f", "#123b3f", "#dff7f2", "rgba(255,255,255,.12)", "#2dd4bf"],
elements: [
{ type: "band", cls: "top" },
{ type: "tileField", x: 126, y: 900, w: 280, h: 280, r: -6, tile: "rgba(223,247,242,.68)" },
{ type: "gridPlate", x: 760, y: 180, w: 256, h: 256 },
{ type: "beam", x: 160, y: 700, w: 880, r: 0 },
{ type: "phone", img: screenshot("phone", 1), x: 366, y: 152, w: 470, r: 0 }
]
},
{
id: "pqs-vertical-02-gallery",
size: [1200, 1500],
theme: ["#eef7ff", "#d7efff", "#ffffff", "rgba(85,185,255,.2)", "#55b9ff"],
elements: [
{ type: "rail", x: -146, y: 248, w: 1510, h: 188, r: -14 },
{ type: "panel", x: 128, y: 1004, w: 380, h: 248 },
{ type: "gridPlate", x: 782, y: 240, w: 230, h: 230 },
{ type: "phone", img: screenshot("phone", 4), x: 366, y: 154, w: 470, r: 0 }
]
},
{
id: "pqs-vertical-03-event",
size: [1200, 1500],
theme: ["#07111f", "#0f766e", "#dff7f2", "rgba(45,212,191,.16)", "#2dd4bf"],
elements: [
{ type: "ticketStrip", x: 122, y: 178, count: 3, r: -4 },
{ type: "ticketStrip", x: 800, y: 902, count: 3, r: 4 },
{ type: "beam", x: 186, y: 722, w: 828, r: 0 },
{ type: "phone", img: screenshot("phone", 7), x: 366, y: 152, w: 470, r: 0 }
]
},
{
id: "pqs-vertical-04-privacy",
size: [1200, 1500],
theme: ["#ffffff", "#f2f7ff", "#ffffff", "rgba(96,112,128,.14)", "#155e63"],
elements: [
{ type: "band" },
{ type: "shield", x: 122, y: 214, s: 230, fill: "#dff7f2" },
{ type: "history", x: 782, y: 866, w: 292, count: 3 },
{ type: "chips", x: 136, y: 1020, chipW: 300, colors: ["#dff7f2", "#f2f7ff", "#ffffff"] },
{ type: "phone", img: screenshot("phone", 8), x: 366, y: 154, w: 470, r: 0 }
]
}
];
function px(v) {
return `${v}px`;
}
function setBox(el, item) {
if (item.x !== undefined) el.style.left = px(item.x);
if (item.y !== undefined) el.style.top = px(item.y);
if (item.w !== undefined) el.style.width = px(item.w);
if (item.h !== undefined) el.style.height = px(item.h);
if (item.r !== undefined) el.style.setProperty("--r", `${item.r}deg`);
}
function tilePattern(count) {
return Array.from({ length: count }, (_, index) => {
const span = document.createElement("span");
if ([1, 4, 7, 10, 13, 16, 20, 24, 31, 36, 38, 43, 48, 51, 57, 64, 67, 71, 78].includes(index)) {
span.style.visibility = "hidden";
}
if (index % 11 === 0) span.style.setProperty("--o", ".38");
return span;
});
}
function renderElement(item) {
let el;
if (item.type === "band") {
el = document.createElement("div");
el.className = `band ${item.cls || ""}`;
}
if (item.type === "rail") {
el = document.createElement("div");
el.className = "rail";
setBox(el, item);
}
if (item.type === "panel") {
el = document.createElement("div");
el.className = "panel";
setBox(el, item);
}
if (item.type === "tileField") {
el = document.createElement("div");
el.className = "tile-field";
setBox(el, item);
el.style.setProperty("--tile", item.tile);
tilePattern(81).forEach((span) => el.appendChild(span));
}
if (item.type === "gridPlate") {
el = document.createElement("div");
el.className = "grid-plate";
setBox(el, item);
tilePattern(36).forEach((span) => el.appendChild(span));
}
if (item.type === "beam") {
el = document.createElement("div");
el.className = "scan-beam";
setBox(el, { ...item, h: 8 });
}
if (item.type === "codeStack") {
el = document.createElement("div");
el.className = "code-stack";
setBox(el, item);
for (let i = 0; i < item.count; i += 1) {
const card = document.createElement("div");
card.className = "code-card";
card.style.width = px(item.w - i * 18);
card.style.marginLeft = px(i % 2 ? 22 : 0);
el.appendChild(card);
}
}
if (item.type === "ticketStrip") {
el = document.createElement("div");
el.className = "ticket-strip";
setBox(el, item);
for (let i = 0; i < item.count; i += 1) {
const ticket = document.createElement("div");
ticket.className = "ticket";
ticket.style.marginLeft = px(i % 2 ? 28 : 0);
el.appendChild(ticket);
}
}
if (item.type === "wifi") {
el = document.createElement("div");
el.className = "wifi-mark";
setBox(el, item);
el.style.setProperty("--s", px(item.s));
el.style.setProperty("--wifi-color", item.color);
const dot = document.createElement("div");
dot.className = "wifi-dot";
el.appendChild(dot);
}
if (item.type === "shield") {
el = document.createElement("div");
el.className = "shield";
setBox(el, item);
el.style.setProperty("--s", px(item.s));
el.style.setProperty("--shield-fill", item.fill);
}
if (item.type === "chips") {
el = document.createElement("div");
el.className = "chips";
setBox(el, item);
el.style.setProperty("--chip-w", px(item.chipW));
item.colors.forEach((color, index) => {
const chip = document.createElement("div");
chip.className = "chip";
chip.style.setProperty("--chip-bg", color);
chip.style.marginLeft = px(index % 2 ? 24 : 0);
el.appendChild(chip);
});
}
if (item.type === "history") {
el = document.createElement("div");
el.className = "history-steps";
setBox(el, item);
el.style.setProperty("--step-w", px(item.w));
for (let i = 0; i < item.count; i += 1) {
const step = document.createElement("div");
step.className = "step";
step.style.marginLeft = px(i % 2 ? 26 : 0);
el.appendChild(step);
}
}
if (item.type === "phone") {
el = document.createElement("div");
el.className = "device";
setBox(el, item);
el.style.setProperty("--dw", px(item.w));
const img = document.createElement("img");
img.src = item.img;
img.alt = "";
el.appendChild(img);
}
if (item.type === "tablet") {
el = document.createElement("div");
el.className = "tablet";
setBox(el, item);
el.style.setProperty("--tw", px(item.w));
const img = document.createElement("img");
img.src = item.img;
img.alt = "";
el.appendChild(img);
}
return el;
}
function renderCreative(creative) {
const stage = document.getElementById("stage");
const [w, h] = creative.size;
document.documentElement.style.setProperty("--w", px(w));
document.documentElement.style.setProperty("--h", px(h));
stage.style.setProperty("--base", creative.theme[0]);
stage.style.setProperty("--band", creative.theme[1]);
stage.style.setProperty("--panel-bg", creative.theme[2]);
stage.style.setProperty("--panel-border", creative.theme[3]);
stage.style.setProperty("--tile", creative.theme[4]);
document.title = creative.id;
creative.elements.forEach((item) => stage.appendChild(renderElement(item)));
const light = document.createElement("div");
light.className = "soft-light";
stage.appendChild(light);
}
const params = new URLSearchParams(location.search);
const requested = params.get("creative");
const creative = creatives.find((item) => item.id === requested);
if (creative) {
renderCreative(creative);
window.__creativeReady = true;
} else {
const list = document.createElement("div");
list.className = "fallback-list";
list.innerHTML = `<strong>Available creatives</strong><br>${creatives.map((item) => item.id).join("<br>")}`;
document.body.appendChild(list);
}
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@@ -0,0 +1,945 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="ad.orientation" content="portrait,landscape">
<title>Private QR Scanner</title>
<link rel="preload" as="image" href="assets/app-icon.png">
<link rel="preload" as="image" href="assets/phone-scanner.jpg">
<link rel="preload" as="image" href="assets/phone-result.jpg">
<link rel="preload" as="image" href="assets/phone-warning.jpg">
<link rel="preload" as="image" href="assets/phone-image.jpg">
<link rel="preload" as="image" href="assets/phone-history.jpg">
<link rel="preload" as="image" href="assets/phone-batch.jpg">
<link rel="preload" as="image" href="assets/phone-settings.jpg">
<script type="text/javascript" src="https://tpc.googlesyndication.com/pagead/gadgets/html5/api/exitapi.js"></script>
<style>
:root {
--deep: #07111f;
--navy: #0b1220;
--teal-900: #123b3f;
--teal-800: #155e63;
--teal-700: #0f766e;
--teal-300: #2dd4bf;
--mint: #dff7f2;
--paper: #f6fbfa;
--surface: #ffffff;
--soft-blue: #f2f7ff;
--line: #dce8e6;
--muted: #607080;
--amber: #ffc857;
--coral: #ff6b5f;
--sky: #55b9ff;
--violet: #8b7cf6;
--shadow: rgba(7, 17, 31, 0.28);
}
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
color: var(--navy);
background: var(--paper);
font-family: Arial, Helvetica, sans-serif;
letter-spacing: 0;
-webkit-font-smoothing: antialiased;
user-select: none;
touch-action: manipulation;
}
button {
font: inherit;
letter-spacing: 0;
border: 0;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.ad {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
isolation: isolate;
background:
linear-gradient(135deg, rgba(255,255,255,0.96), rgba(242,247,255,0.96) 40%, rgba(223,247,242,0.92)),
var(--paper);
}
.band {
position: absolute;
z-index: 0;
left: -16vw;
right: -16vw;
bottom: -13vh;
height: 40vh;
background: linear-gradient(135deg, var(--teal-800), var(--teal-900));
transform: skewY(-8deg);
}
.band-top {
position: absolute;
z-index: 0;
left: -18vw;
right: -18vw;
top: -16vh;
height: 34vh;
background: linear-gradient(135deg, rgba(85,185,255,0.25), rgba(45,212,191,0.26));
transform: skewY(-7deg);
}
.qr-field,
.ticket-stack,
.scan-beam,
.shield,
.wifi,
.mini-list {
position: absolute;
z-index: 1;
pointer-events: none;
}
.qr-field {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-auto-rows: 1fr;
gap: 5px;
width: 132px;
height: 132px;
padding: 9px;
border-radius: 16px;
background: rgba(255,255,255,0.72);
box-shadow: 0 18px 46px rgba(7,17,31,0.12);
transform: rotate(-8deg);
}
.qr-field span {
border-radius: 2px;
background: var(--navy);
}
.qr-field span:nth-child(3n),
.qr-field span:nth-child(11n) {
background: var(--teal-300);
}
.qr-field span:nth-child(5n),
.qr-field span:nth-child(8n),
.qr-field span:nth-child(13n) {
opacity: 0;
}
.ticket-stack {
display: grid;
gap: 10px;
width: 168px;
transform: rotate(5deg);
}
.ticket {
height: 54px;
border-radius: 12px;
background: var(--surface);
border: 1px solid rgba(96,112,128,0.16);
box-shadow: 0 14px 34px rgba(7,17,31,0.12);
position: relative;
overflow: hidden;
}
.ticket:before {
content: "";
position: absolute;
left: 14px;
top: 13px;
width: 28px;
height: 28px;
background:
linear-gradient(var(--navy) 0 0) 0 0 / 10px 10px,
linear-gradient(var(--navy) 0 0) 18px 0 / 10px 10px,
linear-gradient(var(--teal-300) 0 0) 0 18px / 10px 10px,
linear-gradient(var(--navy) 0 0) 18px 18px / 10px 10px;
background-repeat: no-repeat;
}
.ticket:after {
content: "";
position: absolute;
left: 56px;
top: 16px;
width: 84px;
height: 20px;
border-radius: 999px;
background: var(--line);
box-shadow: 0 18px 0 -5px rgba(45,212,191,0.38);
}
.scan-beam {
width: 52vw;
height: 6px;
border-radius: 999px;
background: linear-gradient(90deg, transparent, var(--teal-300), transparent);
box-shadow: 0 0 30px rgba(45,212,191,0.9);
opacity: 0.92;
animation: beamPulse 1.7s ease-in-out infinite;
}
@keyframes beamPulse {
0% { transform: translateY(-10px); opacity: 0.45; }
50% { transform: translateY(10px); opacity: 1; }
100% { transform: translateY(-10px); opacity: 0.45; }
}
.shield {
width: 80px;
height: 100px;
background: var(--amber);
clip-path: polygon(50% 0, 100% 18%, 100% 55%, 50% 100%, 0 55%, 0 18%);
box-shadow: 0 16px 34px rgba(7,17,31,0.16);
}
.shield:after {
content: "";
position: absolute;
left: 30%;
top: 30%;
width: 40%;
height: 24%;
border: solid var(--navy);
border-width: 0 0 8px 8px;
transform: rotate(-45deg);
}
.wifi {
width: 92px;
height: 92px;
opacity: 0.75;
}
.wifi:before,
.wifi:after,
.wifi-dot {
content: "";
position: absolute;
right: 0;
bottom: 0;
border: solid var(--teal-700);
border-width: 8px 8px 0 0;
border-radius: 0 100% 0 0;
transform: rotate(-45deg);
transform-origin: 100% 100%;
}
.wifi:before {
width: 92px;
height: 92px;
}
.wifi:after {
width: 48px;
height: 48px;
}
.wifi-dot {
width: 16px;
height: 16px;
border: 0;
border-radius: 50%;
background: var(--teal-700);
transform: none;
}
.mini-list {
display: grid;
gap: 8px;
width: 166px;
}
.mini-row {
height: 38px;
border-radius: 10px;
background: rgba(255,255,255,0.84);
box-shadow: 0 12px 28px rgba(7,17,31,0.1);
border: 1px solid rgba(96,112,128,0.12);
position: relative;
}
.mini-row:before {
content: "";
position: absolute;
left: 12px;
top: 11px;
width: 18px;
height: 18px;
border-radius: 5px;
background: var(--teal-300);
}
.mini-row:after {
content: "";
position: absolute;
left: 42px;
top: 13px;
width: 92px;
height: 12px;
border-radius: 999px;
background: var(--line);
}
.brand,
.copy,
.phone-wrap,
.panel {
position: absolute;
z-index: 3;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand img {
width: 48px;
height: 48px;
border-radius: 12px;
box-shadow: 0 10px 22px rgba(7,17,31,0.15);
flex: 0 0 auto;
}
.brand strong {
display: block;
color: var(--navy);
font-size: 16px;
line-height: 1.05;
}
.brand span {
display: block;
margin-top: 3px;
color: var(--muted);
font-size: 12px;
line-height: 1.1;
}
.copy h1 {
margin: 0;
color: var(--navy);
font-size: 32px;
line-height: 1.02;
font-weight: 800;
}
.copy p {
margin: 12px 0 0;
color: var(--muted);
font-size: 15px;
line-height: 1.34;
max-width: 330px;
}
.pills {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
max-width: 330px;
}
.pill {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 6px 10px;
border-radius: 8px;
background: rgba(255,255,255,0.74);
color: var(--teal-900);
border: 1px solid rgba(96,112,128,0.14);
font-size: 12px;
line-height: 1.15;
font-weight: 700;
box-shadow: 0 10px 24px rgba(7,17,31,0.07);
}
.phone-wrap {
border-radius: 32px;
padding: 11px;
background: linear-gradient(180deg, #111827, #030712);
box-shadow:
0 34px 72px rgba(7,17,31,0.34),
inset 0 0 0 1px rgba(255,255,255,0.16);
}
.phone-wrap:before {
content: "";
position: absolute;
top: 6px;
left: 50%;
width: 58px;
height: 8px;
border-radius: 999px;
background: #020617;
transform: translateX(-50%);
z-index: 2;
}
.phone-screen {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 23px;
background: var(--deep);
}
.panel {
border-radius: 20px;
padding: 16px;
background: rgba(255,255,255,0.88);
border: 1px solid rgba(96,112,128,0.14);
box-shadow: 0 24px 54px rgba(7,17,31,0.16);
backdrop-filter: blur(8px);
}
.panel-kicker {
color: var(--teal-700);
font-size: 11px;
line-height: 1;
font-weight: 800;
text-transform: uppercase;
}
.panel h2 {
margin: 8px 0 0;
color: var(--navy);
font-size: 20px;
line-height: 1.08;
}
.panel p {
margin: 8px 0 0;
color: var(--muted);
font-size: 13px;
line-height: 1.32;
}
.controls {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-top: 14px;
}
.control-btn {
width: 42px;
height: 42px;
border-radius: 12px;
background: var(--soft-blue);
color: var(--navy);
font-size: 22px;
line-height: 1;
font-weight: 800;
box-shadow: inset 0 0 0 1px rgba(96,112,128,0.16);
}
.dots {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
flex: 1 1 auto;
}
.dot {
width: 7px;
height: 7px;
border-radius: 999px;
background: #b7c7d8;
}
.dot.active {
width: 20px;
background: var(--teal-700);
}
.cta {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
min-width: 0;
min-height: 46px;
margin-top: 14px;
padding: 12px 18px;
border-radius: 14px;
background: linear-gradient(135deg, var(--teal-700), var(--teal-800));
color: #ffffff;
font-size: 14px;
line-height: 1;
font-weight: 800;
box-shadow: 0 18px 34px rgba(15,118,110,0.28);
}
.glint {
position: absolute;
inset: 0;
z-index: 5;
pointer-events: none;
background:
linear-gradient(120deg, rgba(255,255,255,0.34), transparent 24%),
linear-gradient(0deg, rgba(255,255,255,0.14), transparent 42%);
mix-blend-mode: soft-light;
}
@media (orientation: portrait) {
.brand {
left: 26px;
top: 24px;
}
.copy {
left: 28px;
right: 28px;
top: 92px;
}
.copy h1 {
max-width: 300px;
font-size: 31px;
}
.phone-wrap {
left: 50%;
top: 276px;
width: 226px;
height: 402px;
transform: translateX(-50%);
}
.panel {
left: 22px;
right: 22px;
bottom: 20px;
}
.qr-field {
right: 16px;
top: 188px;
}
.ticket-stack {
left: 20px;
top: 386px;
}
.shield {
right: 44px;
bottom: 214px;
}
.wifi {
left: 32px;
bottom: 184px;
}
.mini-list {
right: 22px;
top: 508px;
}
.scan-beam {
left: 24vw;
top: 496px;
}
}
@media (orientation: landscape) {
.brand {
left: 34px;
top: 30px;
}
.copy {
left: 36px;
top: 100px;
width: 32vw;
}
.copy h1 {
max-width: 330px;
font-size: 30px;
}
.phone-wrap {
left: 50%;
top: 50%;
width: 178px;
height: 316px;
transform: translate(-50%, -50%);
}
.panel {
right: 26px;
top: 54px;
width: 30vw;
max-width: 280px;
}
.qr-field {
left: 38vw;
top: 34px;
width: 116px;
height: 116px;
}
.ticket-stack {
right: 32px;
bottom: 22px;
}
.shield {
right: 22vw;
bottom: 40px;
}
.wifi {
left: 28vw;
bottom: 24px;
}
.mini-list {
right: 25vw;
top: 24px;
}
.scan-beam {
left: 28vw;
top: 50%;
width: 45vw;
}
}
@media (max-width: 340px) and (orientation: portrait) {
.brand {
top: 16px;
left: 18px;
}
.brand div {
display: none;
}
.brand img {
width: 40px;
height: 40px;
}
.brand strong {
font-size: 14px;
}
.brand span {
font-size: 11px;
}
.copy {
left: 20px;
right: 158px;
top: 74px;
}
.copy h1 {
font-size: 25px;
}
.copy p,
.pills {
display: none;
}
.phone-wrap {
left: calc(100% - 86px);
top: 96px;
width: 132px;
height: 235px;
}
.panel {
left: 14px;
right: 14px;
bottom: 12px;
padding: 13px;
}
.panel p,
.controls {
display: none;
}
.panel h2 {
font-size: 18px;
}
.cta {
min-height: 40px;
font-size: 13px;
margin-top: 10px;
}
.qr-field,
.mini-list {
display: none;
}
.scan-beam {
top: 232px;
width: 58vw;
}
}
@media (max-height: 360px) and (orientation: landscape) {
.brand {
left: 22px;
top: 16px;
}
.brand div {
display: none;
}
.brand img {
width: 40px;
height: 40px;
}
.brand span,
.pills,
.copy p {
display: none;
}
.copy {
left: 22px;
top: 74px;
width: 34vw;
}
.copy h1 {
font-size: 24px;
}
.phone-wrap {
width: 140px;
height: 249px;
}
.panel {
right: 18px;
top: 18px;
width: 30vw;
padding: 12px;
}
.panel p,
.controls {
display: none;
}
.panel h2 {
font-size: 17px;
}
.panel p {
font-size: 12px;
}
.cta {
min-height: 40px;
margin-top: 10px;
}
.qr-field,
.ticket-stack,
.mini-list {
display: none;
}
}
</style>
</head>
<body>
<div class="ad" id="ad">
<div class="band-top"></div>
<div class="band"></div>
<div class="qr-field" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="ticket-stack" aria-hidden="true">
<div class="ticket"></div>
<div class="ticket"></div>
<div class="ticket"></div>
</div>
<div class="mini-list" aria-hidden="true">
<div class="mini-row"></div>
<div class="mini-row"></div>
<div class="mini-row"></div>
</div>
<div class="shield" aria-hidden="true"></div>
<div class="wifi" aria-hidden="true"><div class="wifi-dot"></div></div>
<div class="scan-beam" aria-hidden="true"></div>
<div class="brand">
<img src="assets/app-icon.png" alt="">
<div>
<strong>Private QR Scanner</strong>
<span>No ads. No tracking.</span>
</div>
</div>
<section class="copy" aria-label="Private QR Scanner campaign">
<h1>Scan QR codes privately</h1>
<p>Read codes on device, inspect links, and keep optional history local.</p>
<div class="pills">
<span class="pill">QR and barcodes</span>
<span class="pill">Image scan</span>
<span class="pill">Local warnings</span>
</div>
</section>
<div class="phone-wrap" aria-hidden="true">
<img class="phone-screen" id="phoneScreen" src="assets/phone-scanner.jpg" alt="">
</div>
<section class="panel" aria-label="Feature preview">
<div class="panel-kicker" id="stepNumber">Step 1 of 7</div>
<h2 id="stepTitle">Live camera scanning</h2>
<p id="stepCopy">Aim at a QR code or barcode and review clear results before taking action.</p>
<div class="controls">
<button class="control-btn" id="prevButton" type="button" aria-label="Previous feature">&#8249;</button>
<div class="dots" id="dots" aria-hidden="true"></div>
<button class="control-btn" id="nextButton" type="button" aria-label="Next feature">&#8250;</button>
</div>
<button class="cta" id="cta" type="button">Try the app</button>
</section>
<div class="glint"></div>
</div>
<script>
var slides = [
{
image: "assets/phone-scanner.jpg",
title: "Live camera scanning",
copy: "Aim at a QR code or barcode and review clear results before taking action."
},
{
image: "assets/phone-result.jpg",
title: "Useful result actions",
copy: "Copy, share, open links, add contacts, or use the action that matches the scan."
},
{
image: "assets/phone-warning.jpg",
title: "Local link warnings",
copy: "Pause before opening unusual links with on-device URL risk checks."
},
{
image: "assets/phone-image.jpg",
title: "Scan from an image",
copy: "Choose a saved picture and detect QR codes or barcodes inside it."
},
{
image: "assets/phone-history.jpg",
title: "Optional local history",
copy: "Keep scan records only when you want them, stored locally on your device."
},
{
image: "assets/phone-batch.jpg",
title: "Batch scanning",
copy: "Capture multiple codes in one flow for events, tickets, or fast workflows."
},
{
image: "assets/phone-settings.jpg",
title: "Simple controls",
copy: "Choose history, warnings, feedback, and app behavior in one clear settings view."
}
];
var index = 0;
var phoneScreen = document.getElementById("phoneScreen");
var stepNumber = document.getElementById("stepNumber");
var stepTitle = document.getElementById("stepTitle");
var stepCopy = document.getElementById("stepCopy");
var dots = document.getElementById("dots");
var prevButton = document.getElementById("prevButton");
var nextButton = document.getElementById("nextButton");
var cta = document.getElementById("cta");
function makeDots() {
var fragment = document.createDocumentFragment();
var i;
for (i = 0; i < slides.length; i += 1) {
var dot = document.createElement("span");
dot.className = "dot";
fragment.appendChild(dot);
}
dots.appendChild(fragment);
}
function renderSlide() {
var slide = slides[index];
var dotNodes = dots.getElementsByClassName("dot");
var i;
phoneScreen.src = slide.image;
stepNumber.innerHTML = "Step " + (index + 1) + " of " + slides.length;
stepTitle.innerHTML = slide.title;
stepCopy.innerHTML = slide.copy;
for (i = 0; i < dotNodes.length; i += 1) {
dotNodes[i].className = i === index ? "dot active" : "dot";
}
}
function move(delta) {
index = (index + delta + slides.length) % slides.length;
renderSlide();
}
function exitAd() {
if (window.ExitApi && typeof window.ExitApi.exit === "function") {
window.ExitApi.exit();
}
}
makeDots();
renderSlide();
prevButton.addEventListener("click", function(event) {
event.stopPropagation();
move(-1);
});
nextButton.addEventListener("click", function(event) {
event.stopPropagation();
move(1);
});
cta.addEventListener("click", function(event) {
event.stopPropagation();
exitAd();
});
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB